Run powershell script for each folder with subfolders - powershell

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.

Related

Trying to return filetype for all users appdata using PowerShell

I am struggling with a PowerShell script to return .json file types in all users %APPDATA% folders. When I run it in ISE, it returns "Get-ChildItem : Access is denied" and when I run ISE as admin, no output is returned.
Here is the script I am working with:
Get-ChildItem C:\Users\*\AppData\Roaming -Filter '*.json' -File -Recurse -Force -ErrorAction SilentlyContinue |
ForEach-Object {
Write-Host $('.json found in {1}' -f $_.Name, ([System.IO.Path]::GetDirectoryName($_.FullName)))
}
Any help would be greatly appreciated!
tl;dr
It is the use of the -File switch that prevents your command from working - see next section.
Given that you're filtering by *.json and that directories are unlikely to match that pattern, you can simply omit it.
If you do need to guard against that, insert a Where-Object { -not $_.PSIsContainer } pipeline segment after the Get-ChildItem call; in PowerShell (Core) 7+, you can simplify to Where-Object -Not PSIsContainer
# Do NOT use -File
# Note: Run from an ELEVATED session (as admin), which is necessary to
# access other users' directories.
Get-ChildItem C:\Users\*\AppData\Roaming -Filter '*.json' -Recurse -Force -ErrorAction SilentlyContinue |
ForEach-Object { '{0} found in {1}' -f $_.Name, $_.DirectoryName }
The reason that Get-ChildItem's -File doesn't work in your case is that you're combining it with a -Path value that is an actual wildcard expression rather than a literal path:
The wildcard expression is resolved first and - perhaps surprisingly - if directories are among the matching paths, they are returned as themselves, rather than listing their children (what is inside them); the latter only happens with literal input paths.
In your case, only directories match and the -File switch is therefore applied to them, not to their children. Since directories don't match the -File switch (by definition aren't files), there is no output.
Whether or not the -Recurse switch is also present then makes no difference, given that there's nothing to recurse into.
As an aside:
If you wanted to make your command work without -Recurse, -File can be made to work, but only if you append \* to your input wildcard (the positionally implied -Path argument), so as to force enumeration of the children to match the -File and the -Filter against:
# Without recursion and a trailing /*, -File works
Get-ChildItem C:\Users\*\AppData\Roaming\* -File -Filter '*.json'
However, since PowerShell then needs to enumerate all children first, it is then simpler and more efficient to omit -Filter and append its pattern directly to the input wildcard:
# No -Filter, pattern appended to input path
Get-ChildItem C:\Users\*\AppData\Roaming\*.json -File

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.

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

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.

rename multiple folder to Uppercase not files using powershell

I am working on powershell where I have multiple folders i need to change all folders names into Uppercase not the files only folder.
I have tried the below code
Get-ChildItem -Path "C:\Users\Xyz\Desktop\sample" -Recurse | % {
if ($_.Name -cne $_.Name.ToUpper()) { ren $_.FullName $_.Name.ToUpper() }
}
But with this code it was changing only file name but I want to change only dir
for example
foldername (lowerCase)
abc
cab
dab
like this (UPPERCASE)
ABC
CAB
DAB
Thanks in advance
These tricks may not be obvious. How's this? Hmm, that didn't actually work. You can't rename folders to the same thing in upper case in powershell.
# doesn't work!
get-childitem -recurse -directory -path "C:\Users\Xyz\Desktop\sample" |
Rename-Item -NewName { $_.name.toupper() } -whatif
Sometimes calling cmd from powershell just works better. Try this first as a "whatif" to see if it does what you want. And if I really understand the question. All this does is echo strings. This command is just "pretend".
get-childitem -recurse -directory -path "C:\Users\Xyz\Desktop\sample" |
foreach { write-host cmd /c ren $_.fullname $_.name.toupper() }
And if that looks good, this actually does the rename. But maybe make a backup in case something goes wrong. Be able to undo the action.
get-childitem -recurse -directory -path "C:\Users\Xyz\Desktop\sample" |
foreach { cmd /c ren $_.fullname $_.name.toupper() }
To limit Get-ChildItem's output to directories only, use the -Directory switch (PSv3+; in PSv2, pipe to Where-Object { $_.PSIsContainer }
Apart from that, your solution should work, but doesn't due to a conceptual flaw in .NET's System.IO.DirectoryInfo.MoveTo() method (and also System.IO.Directory.Move() method), which PowerShell's Rename-Item cmdlet builds on, as of .NET Core 3.0 preview6 / .NET 4.8:
The method doesn't recognize case variations of directory name as a different name, and fails with Source and destination path must be different.
Curiously, files are not affected, as you've experienced.[1]
Of course, while NTFS is case-insensitive, it is also case-preserving, so it should be possible to rename foo to FOO, for instance.
The problem has been reported in this GitHub issue.
Workaround (PSv4+):
Note: js2010's helpful answer offers another, workaround, based on calling cmd.exe for each input folder to use its ren command. While it is conceptually simpler, the caveat is that this approach of creating a child process for every folder processed is inefficient and slow. That said, for occasional renaming operations that probably won't matter.
$path = 'C:\Users\Xyz\Desktop\sample'
Get-ChildItem -LiteralPath $path -Recurse -Directory |
Where-Object { $_.Name -cne $_.Name.ToUpper() } -PipelineVariable dir |
Rename-Item -PassThru -NewName { [IO.Path]::GetRandomFileName() } |
Rename-Item -NewName { $dir.Name.ToUpper() }
The workaround temporarily renames matching folders to a transient, randomly generated name ([IO.Path]::GetRandomFileName()) and then applies the all-uppercase version of the original name.
The input folder's original state and name are captured via variable $dir, which is "stashed" away for later use via the common -PipelineVariable parameter.
[1] Caveat: When you use Get-ChildItem or Get-Item to report a specific directory or file by its literal name, whatever case variation you specify is reported back, even though the true case variation as stored in the filesystem may differ (File Explorer, by contrast, always shows you the true case). To see the true case, useGet-ChildItem <parentDir> -Filter <name>or, as a quick workaround that may show additional items, however, append * to the name/path. In both cases, the name is treated as a wildcard pattern, and the result of the matching process reflects the true case.
How to double quotes folder names ? Some folders has spaces ...
write-host cmd /c ren $.fullname $.name.toupper()

Copy files in a folder structure to their respective sub-folder with Powershell

I want to move the file "file_to_move.txt"in each folder to their respective "done"-folder.
so the file_to_move.txt in C:\Temp\test\folder1 is moved to C:\Temp\test\folder1\done
and file_to_move.txt in C:\Temp\test\folder2 is moved to C:\Temp\test\folder2\done
...and so on, preferably with a %date%_%time% added to the file-name.
if a folder (like folder4 in the example below) does not have a file_to_move.txt, the script should just ignore it and move on.
folder structure example:
C:\Temp\test\DONE
C:\Temp\test\folder1
C:\Temp\test\folder1\done
C:\Temp\test\folder1\some_other_folder
C:\Temp\test\folder1\some_other_file.txt
C:\Temp\test\folder1\file_to_move.txt
C:\Temp\test\folder2
C:\Temp\test\folder2\done
C:\Temp\test\folder2\some_other_folder
C:\Temp\test\folder2\some_other_file.txt
C:\Temp\test\folder2\file_to_move.txt
C:\Temp\test\folder3
C:\Temp\test\folder3\done
C:\Temp\test\folder3\some_other_folder
C:\Temp\test\folder3\some_other_file.txt
C:\Temp\test\folder3\file_to_move.txt
C:\Temp\test\folder4
C:\Temp\test\folder4\done
C:\Temp\test\folder4\some_other_folder
C:\Temp\test\folder4\some_other_file.txt
I have experimented with a Powershell script even if I'm not very good at it and I dont know it can be done in a standard batch-script.
I have tried this so far:
In a batch-script:
SET ThisScriptsDirectory=%~dp0
SET PowerShellScriptPath=%ThisScriptsDirectory%bin\movescript.ps1
PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& '%PowerShellScriptPath%'"
in the movescript.ps1:
Move-Item C:\Temp\test\*\file_to_move.txt C:\Temp\test\*\done\file_to_move_$(get-date -f yyyyMMdd_HHmmss).txt
But this is not working.
I guess it's not precise enough to work.
As a bonus, can the whole thing be done within the basic script or must we use the external .PS1-file?
You can use the Get-ChildItem cmdlet with a filter to retrieve all file_to_move.txt files recursively from a path. Use the Foreach-Object (alias foreach) to iterate over them and combine the new path using the Join-Path cmdlet. To Copy the Item, you can use the Copy-Item cmdlet:
$itemsToCopy = Get-ChildItem -Path c:\Temp\Test -Filter file_to_move.txt -Recurse
$itemsToCopy | foreach {
$newPath = Join-Path $_.DirectoryName 'done'
New-Item -Path $newPath -ItemType directory -Force | out-null
$_ | Copy-Item -Destination $newPath
}
If you want to add a Timestamp, you could use the Get-Date cmdlet and invoke the ToString method with your desired format on it, example:
(Get-Date).ToString("yyyy-dd-M_HH-mm-ss")
Output:
2016-05-4_15-06-02
You can now concat the filenames using a format string and the $_.Basename and $_.Extension property within your foreach loop. I will leave this as an exercise to you.