For various applications, I need to get the parent folder of some files to use to create other output.
$x = "C:\MyFolder\test.txt"
$parent = Split-Path (Split-Path $x) -Leaf
This works fine, unless I am near the top level.
$x = "C:\test.txt"
$parent = Split-Path (Split-Path $x) -Leaf
This works, and $parent = C:\
but there is no -Leaf
as such. What I am left with could be `D:, E:, F:" or maybe even a network location. What in general would be a good way to deal with these cases?
I could do:
if ($parent.length -eq 3) {
handle-top-level-situation
}
But this feels a little inelegant. Is there a better way to check on a top level when I am checking for a parent folder name in this way?
CodePudding user response:
A solution based on .NET APIs:
Casting your path to
[System.IO.FileInfo]
allows you to get the parent directory name via.Directory.Name
[System.IO.Path]::IsRooted()
tells you whether the name represents a root directory or UNC share.
"C:\MyFolder\test.txt", "C:\test.txt" | ForEach-Object {
$parent = ([IO.FileInfo] $_).Directory.Name
if ([IO.Path]::IsPathRooted($parent)) {
"parent dir. is root path: $parent"
} else {
"parent dir. name: $parent"
}
}
Output:
parent dir. name: MyFolder
parent dir. is root path: C:\
Alternatively, if you want to rule out files in root directories up front, before even trying to extract the parent directory name, you can use a regex with -notmatch
:
"C:\MyFolder\test.txt", "C:\test.txt" | ForEach-Object {
if ($_ -notmatch '^[a-z]:\\. ?\\.|^\\\\. ?\\. ?\\.') {
"file is located in a root dir: $_"
} else {
"parent dir. name: " ([IO.FileInfo] $_).Directory.Name
}
}
Note: The above assumes:
regular full paths (no accidental doubling of
\
such as inc:\\temp
), though the regex could be tweaked to handle them.Windows path separators, i.e.
\
; to make the regex cross-platform, replace all\\
instances with[\\/]
.
CodePudding user response:
This function may be able to help you get what you're looking for, basically, takes a path as input and it determines if it's a File or Folder Path, what's the Parent Folder and if the Path is Top-Level. I don't have network share to test UNC, could only test it against localhost \ 127.0.0.1...
function Test-Something {
param(
[Parameter(Mandatory, ValueFromPipeline)]
[string] $Path
)
process {
$out = [ordered]@{ InputPath = $path }
try {
$checkFolder = [IO.File]::GetAttributes($Path) -band [IO.FileAttributes]::Directory
}
catch {
$checkFolder = $true
if([IO.Path]::GetExtension($Path)) { $checkFolder = $false }
}
if($checkFolder) {
$path = Join-Path $Path ([IO.Path]::DirectorySeparatorChar)
$io = [IO.DirectoryInfo] $Path
$out['Parent'] = $io.Parent.Name
$out['Type'] = 'Directory'
$out['TopLevel'] = ($true, $false)[[bool] $io.Parent]
}
else {
$io = [IO.FileInfo] $Path
$out['Parent'] = $io.Directory.Name
$out['Type'] = 'Archive'
$out['TopLevel'] = ($true, $false)[[bool] $io.Directory]
}
[pscustomobject] $out
}
}
A few examples:
@(
'C:'
'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe'
'C:\doesnotexist'
'C:\doesnotexist\somefile.ext'
'\\localhost\c$\Windows'
'\\127.0.0.1\c$'
) | Test-Something
Output:
InputPath Parent Type TopLevel
--------- ------ ---- --------
C: Directory True
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe v1.0 Archive False
C:\doesnotexist C:\ Directory False
C:\doesnotexist\somefile.ext doesnotexist Archive False
\\localhost\c$\Windows \\localhost\c$ Directory False
\\127.0.0.1\c$ Directory True
CodePudding user response:
One way might be to split the string
$x -split '\\' | Select-Object -First 1
The two slashes are just one slash but the second is required for escaping the slash for the split. It results in "C:" but for networkshares it won't return the servername.
For networkshares and local drives you would need
$x -split '\\' | where {$_} | Select-Object -First 1
that does return drivename or servername
Reading your question again and the second, very good answer, i am not sure if you expected something else. So here my third try what you might have expected
function Split-PathToIndex($path, $index) {
$pathlist = @()
while ($path) {
$pathlist = $path
$path = Split-Path -Path $path -Parent
}
$pathlist[$index]
}
Examples with the expected output after the "#"
$YourPath = "c:\Test\Path\with\multiple\folder\and\files.txt"
Split-PathToIndex $YourPath 0 # c:\Test\Path\with\multiple\folder\and\files.txt
Split-PathToIndex $YourPath 1 # c:\Test\Path\with\multiple\folder\and
Split-PathToIndex $YourPath 2 # c:\Test\Path\with\multiple\folder
Split-PathToIndex $YourPath 3 # c:\Test\Path\with\multiple
Split-PathToIndex $YourPath 4 # c:\Test\Path\with
Split-PathToIndex $YourPath 5 # c:\Test\Path
Split-PathToIndex $YourPath 6 # c:\Test
Split-PathToIndex $YourPath 7 # c:\
Split-PathToIndex $YourPath -1 # c:\
Split-PathToIndex $YourPath -2 # c:\Test
Split-PathToIndex $YourPath -3 # c:\Test\Path
Split-PathToIndex $YourPath -4 # c:\Test\Path\with
Split-PathToIndex $YourPath -5 # c:\Test\Path\with\multiple
Split-PathToIndex $YourPath -6 # c:\Test\Path\with\multiple\folder
Split-PathToIndex $YourPath -7 # c:\Test\Path\with\multiple\folder\and
Split-PathToIndex $YourPath -8 # c:\Test\Path\with\multiple\folder\and\files.txt
Now i think you would like to have Split-PathToIndex $YourPath -2
so you always get the first folder but i am still unsure :D
all three solutions are not validating if it is a valid/reachable path!