List every sub folder in reverse - powershell

I have a UNC path
$path = "\\ad.testxyz.com\corp\technology\software\data\iso"
I need to apply permissions to the following paths:
\\ad.testxyz.com\corp\technology\software\data
\\ad.testxyz.com\corp\technology\software
\\ad.testxyz.com\corp\technology
And I don't want to do anything to \\ad.testxyz.com\corp\technology, or higher.
Here is my code so far. This "works", but I've run into a snag with directories that have spaces in them.
$path = "\\ad.domain.com\corp\technology\test\information\software"
#$path = "\\ad.domain.com\corp\technology\payer information\extracts\item load"
# Split string by "\", remove empty elements
$root = (($path.Split("\")).Split('',[System.StringSplitOptions]::RemoveEmptyEntries))
# Recursively list all top level folders in [path]. (Excludes DFS root)
for ($i=2; $i -lt $root.Length - 1; $i++) {
# Recursively build every directory up the tree
$leaf = "\" + $root[$i]
$branch += $leaf
$trunk = "\\" + $root[0] + "\" + $root[1] + $branch
# do work
Write-Host $trunk
}
Outputs:
\\ad.domain.com\corp\technology
\\ad.domain.com\corp\technology\test
\\ad.domain.com\corp\technology\test\information
But if I try and use a path with spaces in it, I get the following
\\ad.domain.com\corp\technology
\\ad.domain.com\corp\technology\payer
\\ad.domain.com\corp\technology\payer\information
\\ad.domain.com\corp\technology\payer\information\extracts
\\ad.domain.com\corp\technology\payer\information\extracts\item

Figured it out. This works for anyone else.
$path = "\\ad.domain.com\corp\technology\test\payer information\software"
# Split string by "\", remove null elements
$tree = $path.Split([system.io.path]::DirectorySeparatorChar) | ? {$_}
# Recursively list all top level folders in [path]. (Excludes DFS root)
For ($i=2; $i -lt $tree.Length - 1; $i++) {
# Recursively build every directory up the tree
$leaf = "\" + $tree[$i]
$branch += $leaf
$trunk = "\\" + $tree[0] + "\" + $tree[1] + $branch
# do work
Write-Host $trunk
}
Outputs:
\\ad.domain.com\corp\technology
\\ad.domain.com\corp\technology\test
\\ad.domain.com\corp\technology\test\payer information

there are builtin ways to handle path manipulation. [grin]
the simplest would be to use the .Parent property of the DirInfo object you get back from Get-ChildItem or from Get-Item when run against a filesystem. that likely cannot be used since it seems you only have strings to work with.
the next easiest is to use Split-Path and the -Parent switch to jump up one level at a time. that is what the below code does.
$SamplePathList = #(
'\\ad.domain.com\corp\technology\test\information\software'
'\\ad.domain.com\corp\technology\payer information\extracts\item load'
)
$StopAt = '\\ad.domain.com\corp\technology'
$Results = foreach ($SPL_Item in $SamplePathList)
{
$TempPath = $SPL_Item
# send starting path to $Results
$TempPath
while ($TempPath -ne $StopAt)
{
$TempPath = Split-Path -Path $TempPath -Parent
# send to $Results
$TempPath
}
}
$Results
output ...
\\ad.domain.com\corp\technology\test\information\software
\\ad.domain.com\corp\technology\test\information
\\ad.domain.com\corp\technology\test
\\ad.domain.com\corp\technology
\\ad.domain.com\corp\technology\payer information\extracts\item load
\\ad.domain.com\corp\technology\payer information\extracts
\\ad.domain.com\corp\technology\payer information
\\ad.domain.com\corp\technology

Related

How to capture the name of sub folder from directory path in powershell

My script is running in a loop on files and display the path for each file, I need to capture the name of the lower sub folder above the file name or even 2 subfolders (maybe there is a way to capture the needed subfolder or something)
For example:
$file = C:\Users\Bandit\AppData\Local\Temp\9c86ee608bb9477ebb11914c36a5a76d\638110173610123239\IDUDatabase_4.0.0.119\IDUDatabase\test.sql
Currently I know to remove a prefix and to display the relevant path (without the temp and sub temp)
$displayPath = ($file.FullName).Substring($subTemp.Length + 1)
$displayPath
# IDUDatabase_4.0.0.119/IDUDatabase
But its not what I need!!
I want to get the lower subfolder above the file name . in this case I want to get the 'IDUDatabase' (or if it possible to get also the IDUDatabase_4.0.0.119)
Example 2:
C:\Users\Bandit\AppData\Local\Temp\9c86ee608bb9477ebb11914c36a5a76d\638110173610123239\ElcServices_4.0.0.120\ForwarderServiceVersion_8_6x\test.dll
The output should be 'ForwarderServiceVersion_8_6x'
cmdlet version
This way uses pure PowerShell cmdlets...
$path = "C:\Users\Bandit\AppData\Local\Temp\9c86ee608bb9477ebb11914c36a5a76d\638110173610123239\ElcServices_4.0.0.120\ForwarderServiceVersion_8_6x\test.dll"
# get the full path of the parent directory
$parent = Split-Path -Path $path -Parent;
# "C:\Users\Bandit\AppData\Local\Temp\9c86ee608bb9477ebb11914c36a5a76d\638110173610123239\ElcServices_4.0.0.120\ForwarderServiceVersion_8_6x
# get the last part of the above
$name = Split-Path -Path $parent -Leaf;
# ForwarderServiceVersion_8_6x
You can put this together into one expression as follows:
$name = Split-Path -Path (Split-Path -Path $path -Parent) -Leaf;
# ForwarderServiceVersion_8_6x
and you can simplify that a bit more by leaving out optional / default parameters:
$name = Split-Path (Split-Path $path) -Leaf;
# ForwarderServiceVersion_8_6x
dotnet version
Alternatively, if you don't mind a bit of dotnet class interaction you can do this:
$name = $path.Split([System.IO.Path]::DirectorySeparatorChar)[-2];
# ForwarderServiceVersion_8_6x
which splits the full path into an array of components and then returns the second-to-last item.
Update
If you want to select arbitrary parts of the path you can use the dotnet version and extract the directory levels that you're interested in as follows:
$separator = [System.IO.Path]::DirectorySeparatorChar;
# get the last two directories in the path
$name = $path.Split($separator)[-3..-2] -join $separator;
# ElcServices_4.0.0.120\ForwarderServiceVersion_8_6x
# get the last two directories and filename
$name = $path.Split($separator)[-3..-1] -join $separator;
# ElcServices_4.0.0.120\ForwarderServiceVersion_8_6x\test.dll
# get everything except the first $x parts of the path
$x = 7;
$parts = $path.Split($separator);
$name = $parts[$x..($parts.Length-1)] -join $separator;
# 638110173610123239\ElcServices_4.0.0.120\ForwarderServiceVersion_8_6x\test.dll

Powershell to return last existing folder in path

I need some help because test-path only returns $true or $false.
I need to return the last existing folder from $path.
$path = \\test.pl\power\shell\test\company
But the last one that exists is the \shell folder.
So how to get a return value like this:
$existingpath = \\test.pl\power\shell
$notexisting = \test\company
An attempt of solving this using the pipeline:
$path = '\\test.pl\power\shell\test\company'
# Create an array that consists of the full path and all parents
$pathAndParents = for( $p = $path; $p; $p = Split-Path $p ) { $p }
# Find the first existing path
$existingPath = $pathAndParents | Where-Object { Test-Path $_ } | Select-Object -First 1
# Extract the non-existing part of the path
$nonExistingPath = $path.SubString( $existingPath.Length + 1 )
The for loop creates a temporay variable $p so the original path will not be destroyed. At each iteration it outputs variable $p, which is automatically added to the array, due to PowerShell's implicit output behaviour. Then it sets $p to the parent path of $p (Split-Path with a single unnamed argument returns the parent path). The loop exits when there is no parent anymore.
The Where-Object | Select-Object line may seem inefficent, but because of Select-Object argument -First 1 it actually tests only the necessary number of paths. When the first existing path is found, the pipeline will be exited (like a break statement in a loop).
The above was the accepted answer. The following solution has been added later. It is more efficient, because it calls Split-Path only as many times as necessary and doesn't need Where-Object and Select-Object.
$path = '\\test.pl\power\shell\test\company'
# Find the first existing path
for( $existingPath = $path;
$existingPath -and -not (Test-Path $existingPath);
$existingPath = Split-Path $existingPath ) {}
# Extract the non-existing part of the path
$nonExistingPath = $path.SubString( $existingPath.Length + 1 )
You can check each folder path one by one, using Split-Path, Test-Path and a while loop:
$path = '.\test.pl\power\shell\test\company'
# Check folder at current path
while(-not(Test-Path $path)){
# Move on to the parent folder
$path = $path |Split-Path
}
# $path will now be `'.\test.pl\power\shell'`
$path
Further to the first answer, it is preferable to resolve and test the root of the passed path before processing:
$path = '\\test.pl\power\shell\test\company'
$resolvedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path)
$root = Split-Path $resolvedPath -Qualifier
if (Test-Path $root -PathType Container) {
while (-not (Test-Path $resolvedPath -PathType Container)) {
$resolvedPath = Split-Path $resolvedPath
}
} else {
$resolvedPath = ''
}
$resolvedPath
Or using .NET methods:
$root = [System.IO.Path]::GetPathRoot($resolvedPath)
if ([System.IO.Directory]::Exists($root)) {
while (-not [System.IO.Directory]::Exists($resolvedPath)) {
$resolvedPath = [System.IO.Path]::GetDirectoryName($resolvedPath)
}
} else {
$resolvedPath = ''
}
$resolvedPath

Powershell copy files from different directories

I have a script that copies the files from one server to another.
I have basically 3 different server locations, that I want to copy from , and create on another server, a folder for each of the location from the source and the contents inside it.
I made that, but I declared a variable for each source and each folder/destination.
I want to create one variable, and from that it should automatically get each location of the source, and copy everything in the correct location.
Would defining $server= "\path1 , \path2, \path3 " do it and it would go into a foreach loop? and go through each part of the path and copy and paste?
If so, how can I define the destination if I have 1 folder with 3 subfolders each corresponding to one source.
for example \path1 should alwasy put items in the path1destination , \path2 should always put items in the path2destination and so on. basically I want somehow to correlate that for each source path to have a specific path destination and everything should use as less variables as possible.
Anyone can provide and ideas on how to tackle this ? My code works but I had to define $path1 , $path2 , $path3 and so on, and then go for a loop on each, which is great but I need to make it clean and less lines of code .
$server1 = "C:\Users\nicolae.calimanu\Documents\B\"
$server2 = "C:\Users\nicolae.calimanu\Documents\A\" # UNC Path.
$datetime = Get-Date -Format "MMddyyyy-HHmmss"
$server3 = "C:\Users\nicolae.calimanu\Documents\C\" # UNC Path.
foreach ($server1 in gci $server1 -recurse)
{
Copy-Item -Path $server1.FullName -Destination $server2
}
ForEach ( $server2 in $server2 ) {
$curDateTime = Get-Date -Format yyyyMMdd-HHmmss
Get-ChildItem $server2 -Recurse |
Rename-Item -NewName {$_.Basename + '_' + $curDateTime + $_.Extension }
}
foreach ($server2 in gci $server2 -Recurse)
{
Move-Item -path $server2 -destination "C:\Users\nicolae.calimanu\Documents\C"
}
Use a hashtable to create a key-value store for each source and destination. Like so,
# Create entries for each source and destination
$ht = #{}
$o = new-object PSObject -property #{
from = "\\serverA\source"
to = "\\serverB\destination" }
$ht.Add($o.from, $o)
$o = new-object PSObject -property #{
from = "\\serverC\source"
to = "\\serverB\destination2" }
$ht.Add($o.from, $o)
$o = new-object PSObject -property #{
from = "\\servera\source2"
to = "\\serverC\destination" }
$ht.Add($o.from, $o)
# Iterate the collection. For demo, print the copy commands
foreach($server in $ht.keys) { $cmd = $("copy-item {0} {1}" -f $ht.Item($server).from, $ht.Item($server).to); $cmd }
# Sample output
copy-item \\serverA\source \\serverB\destination
copy-item \\servera\source2 \\serverC\destination
copy-item \\serverC\source \\serverB\destination2

How can I find out the exact location where the recursive operation is working?

My problem is, that the string for replacement needs to change according to the folder depth the designated file is located and I don't have a clue how to get that info. I need to work with relative addresses.
I want the script to be run from 2 folder levels above the folder where all the files are that need correcting. So I've set the $path in line 1. That folder suppose to be 'depth 0'. In here, the replacement string needs to be in it's native form -> stylesheet.css.
For files in the folders one level below 'depth 0' the string for replacement needs to be prefixed with ../ once -> ../stylesheet.css.
For files in the folders two level below 'depth 0' the string for replacement needs to be prefixed with ../ twice -> ../../stylesheet.css.
...and so on...
I'm stuck here:
$depth = $file.getDepth($path) #> totally clueless here
I need $depth to contain the number of folders under the root $path.
How can I get this? Here's the rest of my code:
$thisLocation = Get-Location
$path = Join-Path -path $thisLocation -childpath "\Files\depth0"
$match = "findThisInFiles"
$fragment = "stylesheet.css" #> string to be prefixed n times
$prefix = "../" #> prefix n times according to folder depth starting at $path (depth 0 -> don't prefix)
$replace = "" #> this will replace $match in files
$depth = 0
$htmlFiles = Get-ChildItem $path -Filter index*.html -recurse
foreach ($file in $htmlFiles)
{
$depth = $file.getDepth($path) #> totally clueless here
$replace = ""
for ($i=0; $i -lt $depth; $i++){
$replace = $replace + $prefix
}
$replace = $replace + $fragment
(Get-Content $file.PSPath) |
Foreach-Object { $_ -replace $match, $replace } |
Set-Content $file.PSPath
}
Here's a function I've written that uses Split-Path recursively to determine the depth of a path:
Function Get-PathDepth ($Path) {
$Depth = 0
While ($Path) {
Try {
$Parent = $Path | Split-Path -Parent
}
Catch {}
if ($Parent) {
$Depth++
$Path = $Parent
}
else {
Break
}
}
Return $Depth
}
Example usage:
$MyPath = 'C:\Some\Example\Path'
Get-PathDepth -Path $MyPath
Returns 3.
Unfortunately, I had to wrap Split-Path in a Try..Catch because if you pass it the root path then it throws an error. This is unfortunate because it means genuine errors won't cause an exception to occur but can't see a way around this at the moment.
The advantage of working using Split-Path is that you should get a consistent count regardless of whether a trailing \ is used or not.
Here is a way to get the depth in the folder structure for all files in a location. Hope this helps get you in the right direction
New-Item -Path "C:\Logs\Once\Test.txt" -Force
New-Item -Path "C:\Logs\Twice\Folder_In_Twice\Test.txt" -Force
$Files = Get-ChildItem -Path "C:\Logs\" -Recurse -Include *.* | Select-Object FullName
foreach ($File in $Files) {
[System.Collections.ArrayList]$Split_File = $File.FullName -split "\\"
Write-Output ($File.FullName + " -- Depth is " + $Split_File.Count)
}
Output is this just for illustration
C:\Logs\Once\Test.txt -- Depth is 4
C:\Logs\Twice\Folder_In_Twice\Test.txt -- Depth is 5

Cannot figure out why part of my copy commands are working and some are not

Code is at the bottom. Simply put, when I run the code, my .ps1 files get moved no problem, but for some reason any file with "Lec" as a part of its name will pop up this error message:
cp : Cannot find path 'C:\Users\Administrator\Documents\Win213x_Lec_filename.docx' because it does not
exist.
I do not understand why this is happening when it recognizes the file name, and I double checked the exact file is in the directory with the exact name, but my ps1 files have no issue.
$sub1 = "Lectures"
$sub2 = "Labs"
$sub3 = "Assignment"
$sub4 = "Scripts"
$DirectoryName = "test"
$Win213CopyFiles = ls C:\Users\Administrator\Documents\Win213Copy
$count = 0
foreach ($i in $Win213CopyFiles)
{
if ($i -match ".*Lec.*")
{
cp $i C:\Users\Administrator\Documents\test\Lectures
$count = $count + 1
}
elseif ($i -match ".*Lab.*")
{
cp $i C:\Users\Administrator\Documents\$DirectoryName\$sub2
$count = $count + 1
}
elseif ($i -match ".*Assign.*")
{
cp $i C:\Users\Administrator\Documents\$DirectoryName\$sub3
$count = $count + 1
}
elseif ($i -match ".*.ps1")
{
cp $i C:\Users\Administrator\Documents\$DirectoryName\$sub4
$count = $count + 1
}
Write-host "$i"
}
## Step 9: Display a message "<$count> files moved"
###################################
Write-host "$count files moved"
Copy-Item expects String as input. So it calls the ToString() method of the FileInfo object $i. That returns only the file name, not full path. As source directory is not specified the current working directory is used. Solution is to use full path found in fullname property:
cp $i.fullname C:\Users\Administrator\Documents\test\Lectures
From pipeline Copy-Item can handle FileInfo objects correctly, using the fullname property. Which you should remember not to drop if using Select-Object.