Powershell Copying files with varying folders in the path - powershell

Right up front apologies for my lack of knowledge with Powershell. Very new to the language . I need to copy some files located in a certain path to another similar path. For example:
C:\TEMP\Users\<username1>\Documents\<varyingfoldername>\*
C:\TEMP\Users\<username2>\Documents\<varyingfoldername>\*
C:\TEMP\Users\<username3>\Documents\<varyingfoldername>\*
C:\TEMP\Users\<username4>\Documents\<varyingfoldername>\*
etc....
to
C:\Files\Users\<username1>\Documents\<varyingfoldername>\*
C:\Files\Users\<username2>\Documents\<varyingfoldername>\*
C:\Files\Users\<username3>\Documents\<varyingfoldername>\*
C:\Files\Users\<username4>\Documents\<varyingfoldername>\*
etc....
So basically all files and directories from path one need to be copied to the second path for each one of the different paths. The only known constant is the first part of the path like C:\TEMP\Users...... and the first part of the destination like C:\Files\Users.....
I can get all the different paths and files by using:
gci C:\TEMP\[a-z]*\Documents\[a-z]*\
but I am not sure how to then pass what's found in the wildcards so I can use them when I do the copy. Any help would be appreciated here.

This should work:
Get-ChildItem "C:\TEMP\*\Documents\*" | ForEach-Object {
$old = $_.FullName
$new = $_.FullName.Replace("C:\TEMP\Users\","C:\Files\Users\")
Move-Item $old $new
}
For additional complexity in matching folder levels, something like this should work:
Get-ChildItem "C:\TEMP\*\Documents\*" -File | ForEach-Object {
$old = $_.FullName
$pathArray = $old.Split("\") # Splits the path into an array
$new = [system.String]::Join("\", $pathArray[0..1]) # Creates a starting point, in this case C:\Temp
$new += "\" + $pathArray[4] # Appends another folder level, you can change the index to match the folder you're after
$new += "\" + $pathArray[6] # You can repeat this line to keep matching different folders
Copy-Item -Recurse -Force $old $new
}

Related

Find files with partial name match and remove desired file

I have a little over 12000 files that I need to sort through.
18-100-00000-LOD-H.pdf
18-100-00000-LOD-H-1C.pdf
21-200-21197-LOD-H.pdf
21-200-21197-LOD-H-1C.pdf
21-200-21198-LOD-H.pdf
21-200-21198-LOD-H-1C.pdf
I need a way to go through all the files and delete the LOD-H version of the files.
EX:
21-200-21198-LOD-H.pdf
21-200-21198-LOD-H-1C.pdf
With the partial match being the 5 digit code I need a script that would delete the LOD-H case of the partial match.
So far this is what I have but it won't work because I need to supply values for the pattern but since there isn't one set pattern and more like multiple patterns I don't know what to supply it with
$source = "\\Summerhall\GLUONPREP\Market Centers\~Pen Project\Logos\ALL Office Logos"
$destination = "C:\Users\joshh\Documents\EmptySpace"
$toDelete = "C:\Users\joshh\Documents\toDelete"
$allFiles = #(Get-ChildItem $source -File | Select-Object -ExpandProperty FullName)
foreach($file in $allFiles) {
$content = Get-Content -Path $file
if($content | Select-String -SimpleMatch -Quiet){
$dest = $destination
}
else{
$dest = $toDelete
}
}
Any help would be super appreciated, even links to something similar or even links to documentation so I can start piecing a script of my own would be super helpful.
Thank you!
This should work for what you need:
# Get a list of the files with -1C preceeding the extension
$1cFiles = #( ( Get-ChildItem -File "${source}/*-LOD-H-1C.pdf" ).Name )
# Retreive files that match the same pattern without 1C, and iterate over them
Get-ChildItem -File "${source}/*-LOD-H.pdf" | ForEach-Object {
# Get the name of the file if it had the -1C suffix preceeding the .ext
$useName = $_.Name.Insert($_.Name.LastIndexOf('.pdf'), '-1C')
# If the -1C version of the file exists, remove the current (non-1C) file
if( $1cFiles -contains $useName ) {
Remove-Item -Force $_
}
}
Basically, look for the 1C files in $toDelete, then iterate over the non-1C files in $toDelete, removing the non-1C file if adding -1C before the file extension matches an existing file with 1C in the name.

Powershell - Find # of nested folders in long file path of a directory

Can anyone help me w/ a code puzzle in powershell? I'm trying to look at a specific directory on several remote servers, and find the deepest nested subfolder in that directory and then count number of parent folders. Pseudo code below.
$servers = get-content (list of servers) and $path = (targetdir on remote machine)
for each $s in $servers:
find the longest path
count the # of \ (to identify # of subfolders)
Write output to file $Servername $countOfNestedFolders
Sorry I'm just good enough w/ posh to be a little dangerous.
Since you're trying to find the biggest count, it sounds like you'll want to do a comparative. Basically, start with a size of 0 - if the folder you're looking at is bigger than that, then it becomes the biggest. You do this for all the folders until you're left with the biggest folder. Note, this method won't work if there are any ties, but it doesn't sound like that's what you're looking for. I should add this is the main code for looking at a single computer. You can wrap a foreach {$server in $servers} around this for multiple servers.
$folders = Get-ChildItem -Path "C:\Directory" -Directory -Recurse
$n = 0
$biggest = ""
foreach ($folder in $folders)
{
$splitout = $folder.FullName.split("\")
if ($splitout.count -gt $n)
{
$n = $splitout.count
$biggest = $folder
}
}
Write-host "Count $n - $biggest"
here's a slight variant of the "count the path parts" solutions. [grin] it counts the delimiters. if your paths are UNC paths OR local paths, this will still give you the deepest nested dir.
however, it will not work with mixed UNC [\\SysName\ShareName] and local [c:\] paths.
also, it does not remove the starting dir from the result.
also also, i am unsure how you want to count number of parent folders. so i just posted the delimiter count.
what it does ...
sets the top dir to work from
gets the dir delimiter char
creates a regex escaped version of that char
grabs all the dirs in the target dir tree
sorts [in descending order] them by the string length of what is left over when you remove everything except the dir delimiters
grabs the 1st of those dirs
displays the .FullName of that dir
displays the number of dir delimiters in the above string
the code ...
$TargetTopDir = $env:APPDATA
$DirDelim = [System.IO.Path]::DirectorySeparatorChar
$RegexDD = [regex]::Escape($DirDelim)
$DirList = Get-ChildItem -LiteralPath $TargetTopDir -Directory -Recurse
$DeepestNestedDir = ($DirList |
Sort-Object {$_.FullName -replace "[^$RegexDD]"} -Descending)[0]
$DeepestNestedDir.FullName
'DirDelimCount = {0}' -f ($DeepestNestedDir.FullName -replace "[^$RegexDD]").Length
output ...
C:\Users\MyUserName\AppData\Roaming\Thunderbird\Profiles\shkjhmpc.default\extensions\{e2fda1a4-762b-4020-b5ad-a41df1933103}\chrome\calendar-gd\locale\gd\calendar\dialogs
DirDelimCount = 15
This got it done; thanks again for all the help!
$servers = gc C:\serverlist.txt
ForEach ($server in $servers){
$folder = "\\$server\x$\share"
$TargetTopDir = $folder
$DirDelim = [System.IO.Path]::DirectorySeparatorChar
$RegexDD = [regex]::Escape($DirDelim)
$DirList = Get-ChildItem -LiteralPath $TargetTopDir -Directory -Recurse -ErrorAction
SilentlyContinue
$DeepestNestedDir = ($DirList | Sort-Object {$_.FullName -replace "[^$RegexDD]"} -
Descending)[0]
$DepthCount = '{0}' -f ($DeepestNestedDir.FullName -replace "[^$RegexDD]").Length
$arrayItems = #{
"Depth Count" = $DepthCount - 3
"Path Name" = $DeepestNestedDir.FullName
"Server Name" = $server
}
$output= #()
$output += New-Object -TypeName PSObject -Property $arrayItems
$output | Export-CSV C:\Output.csv -NoTypeInformation -Append
}
To solve your core problem:
For a given $path, you can find the maximum directory depth in its subtree - expressed as the number of path separators (\ on Windows, / on Unix) plus one in the full path of the most deeply nested subdirectories inside $path - as follows:
# Outputs the number of path components of the most deeply nested folder in $path.
(Get-ChildItem $path -Recurse -Directory |
Measure-Object -Maximum { ($_.FullName -split '[\\/]').Count }
).Maximum
Note: If you wanted to know the relative depth - relative to $path, add -Name to the Get-ChildItem call and replace $_.FullName with $_ inside the script block ({ ... }) passed to Measure-Object. A result of 0 then means that $path has no subdirectories at all, 1 means that there are only immediate subdirectories, 2 means that the immediate subdirectories have (only) subdirectories themselves, ...
Get-ChildItem -Recurse -Directory $path outputs all subdirectories (-Directory) in the entire subtree of (-Recurse) of directory $path; add -Force to include hidden subdirs. - see Get-ChildItem.
Measure-Object -Maximum { ($_.FullName -split '[\\/]').Count } calculates the count of path separators ([\\/] is a regex that matches both a single \ and / char.) in each directory's full path ($_.FullName) - using a script block {...} as the (implied) -Property argument inside of which $_ represents the input path at hand - and determines the maximum (-Maximum); given that Measure-Object outputs a Microsoft.PowerShell.Commands.GenericMeasureInfo instance, the raw maximum value is accessed via the .Maximum property.
All incidental tasks - applying this calculation to multiple servers, writing the results to server-specific files - can be accomplished with the usual cmdlets (Get-Content, ForEach-Object, Set-Content or Out-File / >).
A faster alternative:
The above command is concise and PowerShell-idiomatic, but somewhat slow.
Here's a significantly faster alternative that uses LINQ and .NET APIs directly:
# Note: Makes sure that $path is a *full* path, because .NET's current
# directory usually differs from PowerShell's.
1 + [Linq.Enumerable]::Max(
([System.IO.Directory]::GetDirectories(
$path, '*', 'AllDirectories'
) -replace '[^\\/]').ForEach('Length')
)
Note: The above invariably includes hidden directories too. In .NET Core / .NET 5+, [System.IO.Directory]::GetDirectories() now provides an additional overload that provides more control over the enumeration.
Listing the maximum-depth directories too:
If you want not just to calculate the maximum depth, but also want to list all directories that have the maximum depth (note that there can be more than one):
# Sample input path.
# Note: Makes sure that $path is a *full* path, because .NET's current
# directory usually differs from PowerShell's.
$path = $PWD
# Extract all directories with the max. depth using Group-Object:
# Group by the calculated depth and extract the last group, which relies on
# Group-Object outputting the results sorted by grouping criteria.
$maxDepthGroup =
[System.IO.Directory]::GetDirectories($path, '*', 'AllDirectories') |
Group-Object { ($_ -split '[\\/]').Count } |
Select-Object -Last 1
# Construct the output object.
[pscustomobject] #{
MaxDepth = $maxDepthGroup.Values[0] # The grouping criterion, i.e. the depth.
MaxDepthDirs = $maxDepthGroup.Group # The paths comprising the group.
}
The output is a custom object with .MaxDepth and .MaxDepthDirs (an array of the full paths of those dirs. that have the max. depth) properties. If you pipe it to Format-List, you'll get something like:
MaxDepth : 6
MaxDepthDirs : {/Users/jdoe/Documents/Ram Dass Audio Collection/The Path of Service, /Users/jdoe/Documents/Ram Dass Audio Collection/Conscious Aging,
/Users/jdoe/Documents/Ram Dass Audio Collection/Cultivating the Heart of Compassion, /Users/jdoe/Documents/Cheatsheets/YAML Ain't
Markup Language_files}

Compress File per file, same name

I hope you are all safe in this time of COVID-19.
I'm trying to generate a script that goes to the directory and compresses each file to .zip with the same name as the file, for example:
sample.txt -> sample.zip
sample2.txt -> sample2.zip
but I'm having difficulties, I'm not that used to powershell, I'm learning and improving this script. In the end it will be a script that deletes files older than X days, compresses files and makes them upload in ftp .. the part of excluding with more than X I've already managed it for days, now I grabbed a little bit on this one.
Last try at moment.
param
(
#Future accept input
[string] $InputFolder,
[string] $OutputFolder
)
#test folder
$InputFolder= "C:\Temp\teste"
$OutputFolder="C:\Temp\teste"
$Name2 = Get-ChildItem $InputFolder -Filter '*.csv'| select Name
Set-Variable SET_SIZE -option Constant -value 1
$i = 0
$zipSet = 0
Get-ChildItem $InputFolder | ForEach-Object {
$zipSetName = ($Name2[1]) + ".zip "
Compress-Archive -Path $_.FullName -DestinationPath "$OutputFolder\$zipSetName"
$i++;
$Name2++
if ($i -eq $SET_SIZE) {
$i = 0;
$zipSet++;
}
}
You can simplify things a bit, and it looks like most of the issues are because in your script example $Name2 will contain a different set of items than the Get-ChildItem $InputFolder will return in the loop (i.e. may have other objects other than .csv files).
The best way to deal with things is to use variables with the full file object (i.e. you don't need to use |select name). So I get all the CSV file objects right away and store in the variable $CsvFiles.
We can additionally use the special variable $_ inside the ForEach-Object which represents the current object. We also can use $_.BaseName to give us the name without the extension (assuming that's what you want, otherwise use $_Name to get a zip with the name like xyz.csv).
So a simplified version of the code can be:
$InputFolder= "C:\Temp\teste"
$OutputFolder="C:\Temp\teste"
#Get files to process
$CsvFiles = Get-ChildItem $InputFolder -Filter '*.csv'
#loop through all files to zip
$CsvFiles | ForEach-Object {
$zipSetName = $_.BaseName + ".zip"
Compress-Archive -Path $_.FullName -DestinationPath "$OutputFolder\$zipSetName"
}

Extract date from a path in powershell

I have a folder called files which has a path like : C:\users\xxxx\desktop\files
Inside this folder are different folders: 2015-12-02, 2015-12-01, 2015-11-30, etc
Inside each folder there are multiple files. I was looking to append the folder date at the end of each file inside the folder. I have written the below script for that:
function checkfile($file) {
$filenm = $file.FullName
return($filenm.Contains('.txt'))
}
function renamefile($file) {
$filenm = $file.Name
$ip = $file.FullName.Substring(34)
$ip1 = $ip.Substring(1,4) + $ip.Substring(6,2) + $ip.Substring(9,2)
$txt = $filenm.Split(".")[1] + "_" + $file.name.Split(".")[3] + "_" + $file.name.Split(".")[4] + "." + $file.name.Split(".")[2] + "." + $ip1
Rename-Item $file.FullName -NewName $txt
}
$sourcepath = "C:\users\xxxx\desktop\files"
$inputfiles = (Get-ChildItem -Path $sourcepath -Recurse) | Where-Object { checkfile $_ }
foreach ($inputfile in $inputfiles) {
renamefile $inputfiles
}
The problem I'm facing is in the above script I have used substring(34) to extract the date from the file path. If for some reason the source path changes (to say : H:\powershell\scripts\files) then 34 will not work.
How can I extract the correct date from the file path irrespective of the full file path?
Why not:
$sourcepath = "C:\users\xxxx\desktop\files"
Get-ChildItem -Path $sourcepath -Include "*.txt" -Recurse | % {
Rename-Item $_ "$($_.BaseName)_$($_.Directory)$($_.Extension)"
}
BaseName is the file name without the extension
Directory is the directory name (your date)
Extension is the file extension (i.e. .txt)
$(...) is used to make sure ... is evaluated properly
% is an alias for ForEach-Object and will iterate over the objects coming from the pipeline.
$_ will hold the current object in the ForEach loop
Here, your checkfile function is replaced by -Include "*.txt".
Example :
C:\users\xxxx\desktop\files\2015-12-02\sample.txt
becomes
C:\users\xxxx\desktop\files\2015-12-02\sample_2015-12-02.txt
Not sure if you need it, but if you want to remove the dashes from the date, you could use:
Rename-Item $_ "$($_.BaseName)_$($_.Directory -replace '-','')$($_.Extension)"
EDIT : OP wished to remove the dashes but append the date after the file extension, so:
$sourcepath = "C:\users\xxxx\desktop\files"
Get-ChildItem -Path $sourcepath -Include "*.txt" -Recurse | % {
Rename-Item $_ "$($_.Name).$($_.Directory.Name -replace '-', '')"
}
The particulars of the problem aren't entirely clear. I gather that the date your are interested in is in the fullpath. You want to extract the date from the path and rename the file, such that the new filename includes that date at the end.
However your script implies that there are at least five periods in the path. But I don't see that mentioned in the OP anywhere.
So there are a few problems and open items I see:
1. What is the syntax of a full path? That includes the five or more periods
2. Will the date always be at the same directory depth? I'm guessing xxxx represents the date. If so the date is the second subdirectory. Will the date always be in the second subdirectory?
3. Related to #2, will there ever be paths that include two or more dates?
Assuming my guesses are correct AND that the date will always be the second subdirectory, then extracting the date would be:
`$dateString = $file.fullpath.split('\')[3]
If some of my guesses are incorrect then please add details to the OP. If #3 is true then you'll need to also explain how to know which date is the correct date to use.
An option that you could do is just cd to each path. Then use Get-ChildItem in each dir, without using -Recurse.
In rename $ip would just be $file, no need for FullName.
For example, define your functions and then:
$sourcefile = 'Whateversourcediris'
cd $sourcefile
$directories = (Get-ChildItem)
foreach ($direct in $directories) {
cd $direct
$inputfiles = (Get-ChildItem)|where-object{checkfile $_}
foreach ($inputfile in $inputfiles) {
renamefile $inputfile
}
cd..
}
Hope this helps.

Powershell Move file to new destination based on trimmed file name

I have a folder where files get dropped, I wish to pull the files from that folder and move to a new folder based on part of the file name. If the new folder is missing then create it.
I have attempted to put together the below, however it throws an error about the path already existing and doesn't move the file.
File names can be any thing with out pattern except the last 16 characters of the file, I am removing these and using the remaining as the folder name.
I am really new to scripting so if i have made a silly mistake explanations are appreciated.
Edit
I have played with different orders of operations, added a "-Force" to the new item command, tried with using "Else" and not "If (!(".
I am now at the point where it proudly displays the new directory and then stops.
Could i add the move-item part to a new for each loop so it is processed after the dir is created and tested? If so how do you arrange the { } parts?
Edit 2
I finally have it working, updated script below, the movie-item command was having issues when running into special characters in file names, in my case it was square brackets. The -literalpath switch fixed that for me.
Thanks every one for your input.
Updated script 3.0
#Set source folder
$source = "D:\test\source\"
#Set destination folder (up one level of true destination)
$dest = "D:\test\dest\"
#Define filter Arguments
$filter = "*.txt"
<#
$sourcefile - finds all files that match the filter in the source folder
$trimpath - leaves $file as is, but gets just the file name.
$string - gets file name from $trimpath and converts to a string
$trimmedstring - Takes string from $trimfile and removes the last 16 char off the end of the string
Test for path, if it exists then move on, If not then create directory
Move file to new destination
#>
pushd $source
$sourcefile = Get-ChildItem $source -Filter $filter
foreach ($file in $sourcefile){
$trimpath = $file | split-path -leaf
$string = $trimpath.Substring(0)
$trimmedstring = $string.Substring(0,$string.Length-16)
If(!(Test-Path -path "$dest\$trimmedstring")){New-Item "$dest\$trimmedstring" -Type directory -Force}
move-Item -literalpath "$file" "$dest\$trimmedstring"
}
You may have to tweak the paths being used but the below should work.
$sourcefiles = ((Get-ChildItem $source -Filter $filter).BaseName).TrimEnd(16)
foreach ($file in $sourcefiles)
{
if(!(Test-Path "$dest\$file")){
New-item -ItemType directory -path "$dest\$file"
}
Move-Item "$source\$file" "$dest\file"
}
I finally have it working, updated script below, the movie-item command was having issues when running into special characters in file names, in my case it was square brackets. The -literalpath switch fixed that for me. Thanks every one for your input.
#Set source folder
$source = "D:\test\source\"
#Set destination folder (up one level of true destination)
$dest = "D:\test\dest\"
#Define filter Arguments
$filter = "*.txt"
<#
$sourcefile - finds all files that match the filter in the source folder
$trimpath - leaves $file as is, but gets just the file name.
$string - gets file name from $trimpath and converts to a string
$trimmedstring - Takes string from $trimfile and removes the last 16 char off the end of the string
Test for path, if it exists then move on, If not then create directory
Move file to new destination
#>
pushd $source
$sourcefile = Get-ChildItem $source -Filter $filter
foreach ($file in $sourcefile){
$trimpath = $file | split-path -leaf
$string = $trimpath.Substring(0)
$trimmedstring = $string.Substring(0,$string.Length-16)
If(!(Test-Path -path "$dest\$trimmedstring")){New-Item "$dest\$trimmedstring" -Type directory -Force}
move-Item -literalpath "$file" "$dest\$trimmedstring"
}