LiteralPath does not work. What am I doing wrong? - powershell

I am a beginner and do not know where the error is in my PowerShell script. After a long Google search and a lot of trial and error, I haven't found a solution.
Without LiteralPath the script works. However, I need LiteralPath so that I can use German letters like "ä" in paths.
$d = Get-ChildItem -LiteralPath "G:\ä\*.jpg" | resolve-path -LiteralPath | get-random -count 200
Move-Item -LiteralPath $d -destination G:\ä\ä

G:\ä\*.jpg is a wildcard pattern, so by definition it won't work with -LiteralPath, which uses its arguments literally (verbatim).
Non-ASCII-range characters such as ä should always work in file paths in PowerShell (with PowerShell-native commands), whether or not you use -Path with a wildcard pattern or -LiteralPath with a literal path.
The following streamlined version of your command should work; if it doesn't, the problem is most likely unrelated to non-ASCII-range characters in the paths:
Get-ChildItem -LiteralPath G:\ä -Filter *.jpg |
Get-Random -Count 200 |
Move-Item -Destination G:\ä\ä -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.

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

Cannot find path _ because it does not exist

I'm trying to create small script in powershell that would move files and directories to correct localizations. I made the following command:
Get-ChildItem -Path '.\list\' | ForEach-Object { if ($($_.Name) -like '*[1]*') {
$file = $($_.Name)
$path = $($_.FullName)
echo "$file ==> $path"
Move-Item -Path $path -Destination .\[1]\}}
and it detects correct files and directories, but doesn't move them.
Then I decided to modify command a bit and create hardlinks instead:
Get-ChildItem -Path '.\list\' | ForEach-Object { if ($($_.Name) -like '*[1]*') {
$file = $($_.Name)
$path = $($_.FullName)
echo "$file ==> $path"
New-Item -Path ".\``[1``]\" -Name $file -Type HardLink -Target "$path"}}
and I received the following response (cut to only 1 loop):
[1] dir1 ==> D:\test_move\list\[1] dir1
New-Item:
Line |
5 | New-Item -Path ".\``[1``]\" -Name $file -Type HardLink -Target "$path …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Cannot find path 'D:\test_move\list\[1] dir1' because it does not exist.
The same error appears both with and without administrative privileges.
What do I have to do to make it work?
Try the following:
Get-ChildItem -LiteralPath .\list -File -Filter '*[1]*' | ForEach-Object {
$file = $_.Name
$path = $_.FullName
"$file ==> $path" # implicit `echo` aka `Write-Output`
New-Item -Force -Type HardLink `
-Path (Join-Path .\[1] $file) `
-Target ([WildcardPattern]::Escape($path)) ` # !! see PowerShell Core comments below
-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.
-Filter '*[1]*' prefilters the Get-ChildItem output to only include files whose name contains substring [1] verbatim, because the -Filter parameter uses a filesystem-native wildcard language that does not treat [ and ] as metacharacters.
By contrast, with PowerShell's more powerful wildcard patterns, '*[1]*' would match any name that contains just 1, because the [...] is interpreted as a character set or range. With the -like, the wildcard matching operator operator, you'd have to use '*`[1`]*' (escaping of the metacharacters to be interpreted verbatim with `) to find verbatim [1] substrings.
-File limits matching items to files, because hardlinks are only supported for files, not also directories.
-Path (Join-Path .\[1] $file) use only a -Path argument - rather than a directory-path-only -Path argument combined with a filename-only -Name argument - which ensures that the argument is treated as a literal (verbatim) path, without interpretation of wildcard metacharacters such as [ and ].
Regrettably, combining -Path with -Name causes the -Path argument to be interpreted as a wildcard pattern.
-Force creates the target directory on demand, if needed, but note that it would also replace any preexisting target file.
Windows PowerShell: ([WildcardPattern]::Escape($path)) escapes the -Target (aka -Value) argument (target path) in order to treat it verbatim, because it is - unfortunately - interpreted as a wildcard pattern. Not performing this escaping prompted the error you saw.
Caveat:
In PowerShell [Core] 7+, a breaking change was approved in GitHub proposal #13136 to - more sensibly - treat the -Target argument as a literal (verbatim) path, in which case you would simply use -Target $path.
However, as of PowerShell 7.2.2 this change isn't implemented yet, and, unfortunately, targeting a path that contains [ and ] is currently broken altogether - see GitHub issue #14534.
As of PowerShell 7.2.2 you must escape twice(!) to make it work: ([WildcardPattern]::Escape([WildcardPattern]::Escape($path)))
Note that many, but not all, file-processing cmdlets offer a -LiteralPath parameter to explicitly pass paths to be taken literally (verbatim), whereas the -Path parameter - usually the implied parameter for the first positional argument - is designed to accept wildcard patterns.
Therefore, your could have made your original approach with Move-Item work as follows:
# Ensure that the target dir. exists.
# No escaping needed for -Path when not combined with -Name.
$null = New-Item -Type Directory -Path .\[1] -Force
# Move the file, targeted with -LiteralPath, there.
# No escaping needed for -Destination.
Move-Item -LiteralPath $path -Destination .\[1]\
Note: Unlike with New-Item, Move-Item's -Force does not create the target directory on demand. On the flip side, Move-Item's -Destination more sensibly interprets its argument literally (verbatim), unlike New-Item's -Target parameter.

Recursive Wildcards in PowerShell

I'm trying to delete files in a specific set of folders with PowerShell. My code currently looks like this:
$tempfolders = #(
"C:\Windows\Temp\*"
"C:\Documents and Settings\*\Local Settings\temp\*"
"C:\Users\*\Appdata\Local\Temp\*"
)
Remove-Item $tempfolders -Recurse -Force -ErrorAction SilentlyContinue
I want to add a new folder to that list, with the following formula:
"C:\users\*\AppData\Local\Program\**\Subfolder"
where ** could be multiple subfolders of unknown length. For example, it could be settings\subsettings or it could be folder\settings\subsettings. Is there a way to do this?
You can feed the full path of each file to a regex; this will return only the files that match your format, which you can then pipe to Remove-Item:
ls "C:\Users" -Recurse -Hidden | Where-Object { $_.FullName -imatch '^C:\\users\\([^\\]+)\\AppData\\Local\\Program\\(.*)\\Subfolder' }
Because regexes are considered write-only, a bit of explanation:
backslashes count as escape characters inside a regex and need to be doubled.
([^\\]+) means one or more of any character except a backslash
.* means zero or more of any character
You can use Get-ChildItem -Directory -Recurse,
This will do exactly what you are asking for. Pipe it from the array to a Remove-Item
#("C:\Windows\Temp\*", "C:\Documents and Settings\*\Local Settings\temp\*", "C:\Users\*\Appdata\Local\Temp\*") |
Get-ChildItem -Directory -Recurse -Force -ErrorAction SilentlyContinue |
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
Just for a heads up in Powershell Piping | is key. PowerShell we love to chain commands.

Windows10/Powershell: How to use -include parameter when using Get-Childitem?

When using "-filter":
Get-ChildItem -file -filter "*.txt" | foreach-object { write-host $_.FullName }
I get a listing of the 4 .txt files that's in the current folder.
I tried using "-include"
Get-ChildItem -file -include *.txt | foreach-object { write-host $_.FullName }
Get-ChildItem -file -include *txt | foreach-object { write-host $_.FullName }
and I get nothing. I tried with and without the "-file" parameter and it makes no difference.
I've looked at various guides/examples (ss64.com/TechNet and etc) and supposedly I am doing it right.
Any ideas what I could be doing wrong? Thanks!
From the Get-Help page for Get-ChildItem:
The -Include parameter is effective only when the command includes the -Recurse parameter or the path leads to the contents of a directory, such as C:\Windows*, where the "*" wildcard character specifies the contents of the C:\Windows directory.
You'll note that you don't get a syntax error if you specify -include and don't specify -recurse in spite of the fact that whatever it does is literally undefined. You'll also note that C:\Windows* is not a normal wildcard expression for "all files in the C:\Windows directory". It's a wildcard expression for "all items that start with 'Windows' in the C:\ directory and may or may not have an extension". I have no idea what the authors of Get-ChildItem think this parameter is supposed to do. They've done a fantastically poor job of documenting it and implementing it.
Consequently, I avoid the -Include parameter as broken/badly documented. I don't know what it's supposed to do that -Filter doesn't. I've read articles about what it does exactly. It "passes the value to the underlying provider to filter at that level" in some manner. I don't know why they assume that a sysadmin will know what that really means. My understanding is that it's the difference between calling DirectoryInfo.GetFiles() on each directory item and calling DirectoryInfo.GetFiles('*.txt') on each directory item, but most sysadmins aren't going to know what that means. However, it's so oddly behaved that I don't trust it, so even though I am about 95% sure of what it does... I still never use it.
Instead, I just pipe to Where-Object:
Get-ChildItem -file | Where-Object Extension -eq '.txt' | [...]
Also note that Get-ChildItem is broken with -LiteralPath, -Recurse and -Include in some versions of PowerShell, and will instead return all items.
Compare:
Get-ChildItem -LiteralPath $PSHOME *.exe -Recurse # works
Get-ChildItem -Path $PSHOME -Include *.exe -Recurse # works
Get-ChildItem -LiteralPath $PSHOME -Include *.exe -Recurse # does NOT work
Issue reported here for v6.
These work for me without recursion:
Get-ChildItem -Path "C:\Users\Athom\Desktop\*.txt"
Get-ChildItem -Path ".\*.txt"
Or Just add the recursion parameter:
Get-ChildItem -Include *.txt -Recurse

Powershell Rename-Item issue with special characters

I've run into the issue I know has been addressed several times here previously but I'm not overly familiar with PS scripts or regular expressions and I'm struggling to implement a fix here.
Basically, I'd be very happy if this line of my script would work:
Get-childItem *.* -recurse -force | % {rename-item $_.name ($_.name -replace '(\d{2}) \[(\d{1})x(\d{2})\]','$1 s0$2e$3')}
And example file name would be "24 [1x01].avi" and should instead be named "24 s01e01.avi" - I'm trying to tidy up my media collection :)
I know the reason it doesn't is the square brackets in the file names. I think i have to move the files to a temp location, changing the name while doing so and then move back. My difficulty is that I haven't been able to find an example of this using the regular expression and I haven't been able to get this to work.
Also, is there a better workaround than this available yet? The bug on Microsoft Connect is closed as fixed?
Thanks!
I think your regular expressions might make more sense (to you), especially as a beginner, if you used "named groups" (a regular expression concept). I've modified your regular expression slightly to take this into account. You should really get familiar with regular expression terminology though, to ensure that you can update your regex to work in all scenarios.
"24 [1x01].avi" -replace '(?<ShowName>.*) \[(?<Season>\d{1})x(?<Episode>\d{2})\]','${ShowName} s0${Season}e${Episode}';
Result:
24 s01e01.avi
Can you give an example of a file name that doesn't work?
EDIT: Attaching example script. Let me know if this works for you.
# 1. Define a test folder path
$RootPath = "$env:SystemDrive\test";
# 2. Create the folder
mkdir -Path $RootPath;
# 3. Create a test file
Set-Content -Path "$RootPath\24 [1x01].txt" -Value '';
# 4. Get a list of files in the directory
$FileList = Get-ChildItem -Path $RootPath;
foreach ($File in $FileList) {
# 5. Fix up the name of each file
$NewName = $File.Name -replace '(?<ShowName>.*) \[(?<Season>\d{1})x(?<Episode>\d{2})\]','${ShowName} s0${Season}e${Episode}';
# 6. Rename the file
Move-Item -Path $File.FullName -Destination ((Split-Path -Path $File.FullName -Parent) + $NewName);
}
powershell Rename-Item fail to rename
If you are running PS 3+ add -LiteralPath switch to your rename:
One of the easiest ways to handle the Special Characters (such as square/block brackets[]) in the file-names, is to simply use the -LiteralPath parameter.
Error: When attempting to rename files or folders that contain square/block brackets [], the standard error message that PowerShell returns is "file not found", which is not accurate.
Reason: Windows still uses old fashioned 8.3 format short-file-names (max 8 chars with limited allowed chars) unfortunately PowerShell's -Path parameter (even in version 5.1) uses these internal names.
Solution: Use the -LiteralPath argument, available for most cmdlets (including Get-ChildItem or Rename-Item etc.)
Examples: Depicting handling of files or folders that contain square/block brackets []:
Get-ChildItem -LiteralPath "test[1].txt";
Test-Path -LiteralPath "C:\dir\test[1].txt";
Rename-Item -LiteralPath "test[1].txt" "test[2].txt";
Note: In PowerShell version below 3.0, to rename files/directories containing special characters, use Move-Item with -LiteralPath, instead of Rename-Item cmdlet because Rename-Item didn't have -LiteralPath in PS version 2.0 (or below).
Thanks to pointers from #Trevor Sullivan I was able to get the desired results by:
Updating to the most recent version of PowerShell (download link available in the comments)
Edited the script to the following:
Get-childItem *.* -recurse -force | Move-Item -Destination {$_ -replace '(\d{2}) \[(\d{1})x(\d{2})\]','$1 s0$2e$3'}