How can we separate folders using powershell script with reference of filename - powershell

I am newbie to powershell scripting and I did go through lot of articles here and not able to get much help.Can someone help me out on how to put files in specific folders:
file name example:
2008_11_chan_3748_NB001052_031_SIGNED.pdf
put it in folders:
2008/11/NB001052-031/....
if it ends with "draft", e.g.
2008_11_chan_3748_NB001052_031_Draft.pdf
put it in a separate draft folder as below
2008/11/NB001052-031/Draft
Any help is really appreciated.
Thanks in advance.

Push-Location c:\2009
Get-ChildItem -File -Filter *_*_*_*_*_*.pdf |
Move-Item -Destination {
$tokens = $_.Name -split '_'
$subdirNames = $tokens[0,1,4]
if ($tokens[-1] -like 'Draft.*') { $subdirNames += 'Draft' }
(New-Item -Force -Type Directory ($subdirNames -join '\')).FullName
} -WhatIf
Pop-Location
Note: The -WhatIf common parameter in the command above previews the move operation, but the target subfolders are created right away in this case.
Remove -WhatIf once you're sure the operation will do what you want.
(Partial) explanation:
The Get-ChildItem call gets those files in the current directory whose name matches wildcard pattern *_*_*_*_*_*.pdf.
Move-Item -Destination uses a delay-bind script block ({ ... }) to dynamically determine the target path based on the current input object, $_, which is of type System.IO.FileInfo
The code inside the script block uses the -split operator to split the file name into tokens by _, extracts the tokens of interest and appends Draft as appropriate, then creates / returns a subdirectory path based on the \-joined tokens joined in order to output the target directory path; note that -Force creates the directory on demand and quietly returns an existing directory.

Related

Remove everything before \

I need to copy a lot of files and use the same sort of folder structure where the files needs to go.
So for instance if I have the following two documents:
\\Server1\Projects\OldProject\English\Text_EN.docx
\\Server1\Projects\OldProject\English\Danish\Text_DA.docx
I would need to move them to a new place on the server, but they need to be in the same "language folder". So I need to move them like this:
\\Server1\Projects\OldProject\English\Text_EN.docx -> \\Server1\Projects\NewProject\English\Text_EN.docx
\\Server1\Projects\OldProject\English\Danish\Text_DA.docx -> \\Server1\Projects\NewProject\English\Danish\Text_DA.docx
The issue here is, that I would need to take names of the "language" folder and create them in the NewProject folder.
How would I be able to take and remove everything before the \, so I end up with only having the "language" folders like English\ and English\Danish
If the goal it to just replace the 'OldProject' folder with 'NewProject' in the file path you could use replace to make the change to the file path:
$filePath = Get-ChildItem \\Server1\Projects\OldProject\English\Text_EN.docx
Copy-Item $filePath.FullName -Destination ($filepath.FullName -replace "\bOldProject\b", "NewProject")
The '\b' is used to do a regex EXACT match for anything inside the tags.
Try the following, which, for each input file:
constructs the target dir. path by replacing the old project dir.'s root path with the new one's, thereby effectively replicating the old dir.'s subdirectory structure.
makes sure that the target dir. exists
then copies the input file to the target dir.
$oldProjectRoot = '\\Server1\Projects\OldProject'
$newProjectRoot = '\\Server1\Projects\NewProject'
Get-ChildItem -Recurse -Filter *.docx -LiteralPath $oldProjectRoot |
ForEach-Object {
# Construct the target dir. path, with the same relative path
# as the input dir. path relative to the old project root.
$targetDir =
$newProjectRoot + $_.Directory.FullName.Substring($oldProjectRoot.Length)
# Create the target dir., if necessary (-Force returns any preexisting dir.)
$null = New-Item -Force -Type Directory $targetDir
$_ # Pass the input file through.
} |
Copy-Item -Destination { $targetDir } -WhatIf
Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.

Searching for only folders that contain a certain folder using powershell

I am trying to use powershell to update some programs for my company. I am writing a script to do so (as instructed). When I install the new version of the program on the machines, it also requires me to 'upgrade' existing folders to match the new version of the software.
I need to find all of the folders that contain a certain hidden folder(let the name of said folder be .blah). I am trying to use the get-childitem command, with -path [drives to check] -Recurse -Directory -Force -EA SilentlyContinue. However, I am not sure how to filter correctly to only find folders that contain the .blah folder inside of it.
Help would be very much appreciated.
Combine your Get-ChildItem call with a Where-Object call that tests for a child directory of a given name using Test-Path:
# Note: "." refers to the *current* directory
# Adjust as needed.
Get-ChildItem -LiteralPath . -Recurse -Directory -Force -ErrorAction Ignore |
Where-Object {
Test-Path -ItemType Container -LiteralPath "$($_.FullName)\.blah"
}
The Get-ChildItem call outputs all directories (-Directory) in the entire directory subtree (-Recurse), including hidden ones (-Force), ignoring any errors (such as from lack of permissions, -ErrorAction Ignore).
The Where-Object call calls Test-Path to look for a .blah child directory (-ItemType Container) in the directory at hand ($_).
With a -LiteralPath argument, Test-Path finds the specified path if it exists, irrespective of whether the target file or directory is hidden.
By contrast, with a wildcard-based -Path argument, hidden items are not found, and given that, as of PowerShell 7.2.5, Test-Path has no -Force switch, there is no way to force their inclusion; this gap in functionality is the subject of GitHub issue #6501.
Note: In PowerShell (Core) 7+, you could simplify "$($_.FullName)\.blah" to "$_\.blah", because the [System.IO.DirectoryInfo] and [System.IO.FileInfo] instances output by Get-ChildItem and Get-Item there consistently stringify to their full path (.FullName) property, unlike in WindowsPowerShell, where they situationally stringify by their file/directory name only - see this answer.

Find and Rename large quantities of files

I am using PowerShell to find, move, and rename a large amount of audit files. These files are in a shared folder with hundreds of gigabytes of extra junk. Manually clicking and dragging would take hours or even days as they are in many nested folders.
All files are currently named the same (audit.log, or audit1.log if there is a second log in the same folder). I need to find those files, copy them to a central location and rename them so they don't overwrite one another (not necessarily in that order).
I am not a programmer by any standard. This is what I have tried so far based on this website:
cd "H:\Flights\SCP\Log Analysis\1st Quarter"
Get-ChildItem -Filter "audit*.log" -Recurse `
| Rename-Item -NewName {$_.Name -replace 'audit', "$_.Fullname"} -WhatIf `
| Move-Item -Destination "H:\Flights\SCP\Log Analysis\Audit logs" -WhatIf
I use -WhatIf to make sure I do not make a mistake since I cannot overwrite the files. My original line of thought was to simply replace the word audit with the file path, but any reasonable method to rename the files in a way which will not overwrite will be helpful.
Theo and Mathias R. Jessen have provided all the crucial pointers in comments:
Rename-Item only accepts a mere name as a -NewName argument.
Move-Item can perform both moving and renaming in a single operation.
Delay-bind script blocks ({ ... }) can be passed to both Rename-Item's -NewName and Move-Item's -Destination parameters, which enable deriving the target name / path dynamically, for each input object ($_)
To put it all together:
Get-ChildItem -Filter audit*.log -Recurse |
Move-Item -Destination {
"H:\Flights\SCP\Log Analysis\Audit logs\$($_.FullName -replace '[:\\/]', '_')"
} -WhatIf
Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.
Note:
The target directory of the move operation must already exist (-Force does not create it for you, it would only allow you to replace an existing file).
$_.FullName -replace '[:\\/]', '_' transforms the full path of the original file into something that can be used as a file name, by replacing :, \ (and /) characters with _.
The caveat is that with long paths you may run into the 256-characters-per-name limit
An alternative is to use an abstract, unique identifier of fixed length, which you can generate with the New-Guid cmdlet, as Mathias suggests.

How to recursively append to file name in powershell?

I have multiple .txt files in folders/their sub-folders.
I want to append _old to their file names.
I tried:
Get-ChildItem -Recurse | Rename-Item -NewName {$_.name -replace '.txt','_old.txt' }
This results in:
Some files get updated correctly
Some files get updated incorrectly - they get _old twice - example: .._old_old.txt
There are few errors: Rename-Item : Source and destination path must be different.
To prevent already renamed files from accidentally reentering the file enumeration and therefore getting renamed multiple times, enclose your Get-ChildItem call in (), the grouping operator, which ensures that all output is collected first[1], before sending the results through the pipeline:
(Get-ChildItem -Recurse) |
Rename-Item -NewName { $_.name -replace '\.txt$', '_old.txt' }
Note that I've used \.txt$ as the regex[2], so as to ensure that only a literal . (\.) followed by string txt at the end ($) of the file name is matched, so as to prevent false positives (e.g., a file named Atxt.csv or even a directory named AtxtB would accidentally match your original regex).
Note: The need to collect all Get-ChildItem output first arises from how the PowerShell pipeline fundamentally works: objects are (by default) sent to the pipeline one by one, and processed by a receiving command as they're being received. This means that, without (...) around Get-ChildItem, Rename-Item starts renaming files before Get-ChildItem has finished enumerating files, which causes problems. See this answer for more information about how the PowerShell pipeline works.
Tip of the hat to Matthew for suggesting inclusion of this information.
However, I suggest optimizing your command as follows:
(Get-ChildItem -Recurse -File -Filter *.txt) |
Rename-Item -NewName { $_.BaseName + '_old' + $_.Extension }
-File limits the the output to files (doesn't also return directories).
-Filter is the fastest way to limit results to a given wildcard pattern.
$_.BaseName + '_old' + $_.Extension uses simple string concatenation via the sub-components of a file name.
An alternative is to stick with -replace:
$_.Name -replace '\.[^.]+$', '_old$&'
Note that if you wanted to run this repeatedly and needed to exclude files renamed in a previous run, add -Exclude *_old.txt to the Get-ChildItem call.
[1] Due to a change in how Get-ChildItem is implemented in PowerShell [Core] 6+ (it now internally sorts the results, which invariably requires collecting them all first), the (...) enclosure is no longer strictly necessary, but this could be considered an implementation detail, so for conceptual clarity it's better to continue to use (...).
[2] PowerShell's -replace operator operates on regexes (regular expressions); it doesn't perform literal substring searches the way that the [string] type's .Replace() method does.
The below command will return ALL files from the current folder and sub-folders within the current directory the command is executed from.
Get-ChildItem -Recurse
Because of this you are also re-turning all the files you have already updated to have the _old suffix.
What you need to do is use the -Include -Exclude paramters of the Get-Childitem Cmdlet in order to ignore files that already have the _old suffix, and meet your include criteria, for example.
Get-ChildItem -Recure -Include "*.txt" -Exclude "*_old"
Then pipe the results into your re-name item command
Get-ChildItem cmdlet explanation can be found here.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-childitem?view=powershell-7

Run powershell script for each folder with subfolders

I have a few scripts that I need to run against a dozen folders all with a relative path. I'm trying to solve this with a master script to run for each folder in that path, one folder at a time. The folders are all children of the "here" folder in the below path. I can't seem to get the syntax right, but I think I'm close :)
Is there a more efficient way to run a script against the contents of every folder in a directory, one folder at a time?
$pdfFolder = 'C:\path\to\folders\here'
$Completed = Get-ChildItem $pdfFolder -Recurse
ForEach-Object ($Completed){
Invoke-Expression -Command "C:\path\where\scriptis\script.ps1"
}`
$pdfFolder = 'C:\path\to\folders\here'
# Get all subfolders - note the -Directory switch (PSv3+)
$Completed = Get-ChildItem $pdfFolder -Recurse -Directory
# Pipe the subfolders to ForEach-Object, invoke the
# script with & (avoid Invoke-Expression), and pass the subfolder
# at hand as an argument.
$Completed | ForEach-Object {
& "C:\path\where\scriptis\script.ps1" $_
}
As for what you tried:
Get-ChildItem $pdfFolder -Recurse
This command returns not just folders (directories), but also files. To limit the output to folders, pass switch -Directory (PSv3+).
ForEach-Object ($Completed) { ... }
You're confusing the syntax of the foreach loop with the syntax of the pipeline-based ForEach-Object cmdlet.
The cmdlet expects input from the pipeline, so you must use $Completed | ForEach-Object { ... } instead.
Also note that unless you truly need to collect all subfolders in an array first, you can simply pipe your Get-ChildItem call directly to ForEach-Object.
Invoke-Expression -Command "C:\path\where\scriptis\script.ps1"
Invoke-Expression should be avoided, because it is rarely the right tool and can be a security risk.
All you need to invoke a script by its quoted and/or stored-in-a-variable file path is to use &, the call operator, as shown above.