Doing operations on file names and directories with powershell - powershell

I'm attempting to do some specific operations with the names of folders and files in a set directory.
I'm replacing underscores ("_") with nothing ("")
I'm replacing spaces (" ") with nothing ("")
I'm replacing lowercase hp ("hp") with uppercase hp and a space ("HP
")
I'm replacing uppercase hp ("HP") with uppercase hp and a space ("HP
")
I'm replacing camelcase hp ("Hp") with uppercase hp and a space ("HP
")
I'm replacing the lowercase letter a ("a") with the uppercase letter
a ("A") ...B...C etc.
This is my powershell script:
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("_","") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace(" ","") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("hp","HP ") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("HP","HP ") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("Hp","HP ") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("&"," & ") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("a","A") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("b","B") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("c","C") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("d","D") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("e","E") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("f","F") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("g","G") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("h","H") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("i","I") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("j","J") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("k","K") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("l","L") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("m","M") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("n","N") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("o","O") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("p","P") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("q","Q") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("r","R") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("s","S") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("t","T") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("u","U") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("v","V") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("w","W") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("x","X") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("y","Y") }
Get-ChildItem -recurse -name | ForEach-Object { Move-Item $_ $_.replace("z","Z") }
I know, it's essentially a bunch of loops.
I had a couple of problems and questions:
The above code does not work for replacing lowercase letters with uppercase letters (a-z), why is that? It also seems to not work all the time recursively either. E.G folder name HP 14-Z040wm, file inside is HP14-Z040WM
How can I ultimately make this script better? As in shorter, faster etc.
(I tagged batch-file as well, as I would definitely accept (my first choice when I started making this) a batch file solution.

First, only get items once, then process them once. Next, you can replace the character substitution with the ToUpper() method. Third, since all those methods return strings, you can just chain the methods together.
It's easy enough for files:
Get-ChildItem -Recurse -File | ForEach-Object {
$NewName = $_.Name.ToUpper().Replace('_','').Replace(' ','').Replace('HP', 'HP ');
Rename-Item -Path $_.FullName -NewName $NewName;
}
Folders, however, are a whole other ball of wax. You can't rename a folder path to the same name, and path names are case-insensitive. So neither Rename-Item nor Move-Item will work alone. You'll have to use a temporary folder name.
Here's code for both files and folders:
#Unique name for temp folder
$TempFolderName = [System.Guid]::NewGuid().Guid;
Get-ChildItem -Recurse | ForEach-Object {
$NewName = $_.Name.ToUpper().Replace('_','').Replace(' ','').Replace('HP', 'HP ');
if (!$_.PSIsContainer) {
#Regular file
Rename-Item -Path $_.FullName -NewName $NewName;
}
else {
#Get the parent folder
$ParentFolder = Split-Path $_.FullName;
#Rename folder to temp name
Rename-Item -Path $_.FullName -NewName (Join-Path $ParentFolder $TempFolderName);
#Rename temp name to folder
Rename-Item -Path (Join-Path $ParentFolder $TempFolderName) -NewName (Join-Path $ParentFolder $NewName);
}
}

You should rename files first then folders. Trying to do both at the same time could invalidate other objects in the pipeline and get you errors. Also the repetition here can be saved with -replace "\s|_" and.ToUpper().-replace` is a regex function that we are using to replace all space and underscores with nothing. You can chain these as well so we are also going to replace HP at the start of the name and add in the space.
$path = "C:\Temp"
Get-ChildItem $path -Recurse -Directory | Rename-Item -NewName {($_.Name).ToUpper() -replace "\s|_"} -WhatIf
Get-ChildItem $path -Recurse -File | Rename-Item -NewName {($_.Name).ToUpper() -replace "\s|_"} -WhatIf
I have put -WhatIf on there for testing. If this code is only going to change the case of the entity names then it is going to give an error that the file/folder name needs to be different (folders in are case insensitive). So you would need to move it to a temporary folder first. Windows is not case sensitive so it will see you trying to name the object the same.
You need to be very careful running this code as it permanently changes folder names to temporary names. If the process fails you would have to manually fix the broken folders. Look for .bak folders in that case. My tests did work however so hopefully it is just a precaution. Run your own tests to be sure.
$path = "C:\Temp"
# Rename folders
Get-ChildItem $path -Recurse -Directory | ForEach-Object{
# Save the current name
$originalName = $_.Name
# Make sure the temp name is unique.
$temp = Rename-Item -Path $_.FullName -NewName "$originalName.bak" -PassThru
# Rename using the saved name and proposed changes.
$temp | Rename-Item -NewName ($originalName.ToUpper() -replace "\s|_" -replace "^HP","HP ")
}
Get-ChildItem $path -Recurse -File | Rename-Item -NewName {$_.Name.ToUpper() -replace "\s|_" -replace "^HP","HP "}

Related

Adding _ at the end of every file name of 3 type of extension in a folder

How could I modify name of every file by adding _ before the filename extension in a Get-ChildItem -Include without calling 3 times a Foreach.
My script works, but I would like to simplify it by using -Include and not -Filter.
function renommer_fichier {
$files = Get-ChildItem -Path C:\mestp\mesFichiers -Filter *.jpg
Foreach ($file in $files)
{
$file | Rename-Item -NewName { $_.Name -replace '.jpg', '_.jpg' }
}
$files = Get-ChildItem -Path C:\mestp\mesFichiers -Filter *.mp3
Foreach ($file in $files)
{
$file | Rename-Item -NewName { $_.Name -replace '.mp3', '_.mp3' }
}
$files = Get-ChildItem -Path C:\mestp\mesFichiers -Filter *.mpeg
Foreach ($file in $files)
{
$file | Rename-Item -NewName { $_.Name -replace '.mpeg', '_.mpeg' }
}
}
Unfortunately, the -Include parameter - which supports specifying multiple patterns - doesn't work as one would intuitively expect:
Use Get-Item instead of Get-ChildItem and append \* to the input path to make -Include work as intended.
See this answer for background information.
Use the .BaseName and .Extension properties in the delay-bind script block you're passing to Rename-Item to facilitate inserting _ before the filename extension.
Get-Item -Path C:\mestp\mesFichiers\* -Include *.jpg, *mp3, *.mpeg |
Rename-Item -WhatIf -NewName { $_.BaseName + '_' + $_.Extension }
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.

powershell script not interpreting file path correctly

I have an issue where a path to a file is generated by an application. So the path looks like this….
Unfortunately, this output is generated from an application…so I cannot manipulate the output.
Now…when I run my powershell script to copy the file …I get the following errors
It seems that the problem is that my powershell script is not getting the path correctly….
$folders = Get-Content -Path '\\TFA-APP-01\CUI\WS\FOUNDFILES\results.txt'
$dest = '\\TFA-APP-01\CUI\WS\FOUNDFILES\found\'
$excludes = "WS","FOUNDFILES"
foreach ($folder in $folders) {
# Get-ChildItem -Path $folder | Where-Object{$_.Name -notin $excludes} | Copy-Item -Destination $dest -Recurse -ErrorAction SilentlyContinue
Get-ChildItem -Path $folder | Where-Object{$_.Name -notin $excludes} | Where-Object{$_.FullName -like 'Desktop'} | Copy-Item -Destination $dest -Recurse -ErrorAction SilentlyContinue
Get-ChildItem -Path $folder | Where-Object{$_.Name -notin $excludes} | Where-Object{$_.FullName -like 'Documents'} | Copy-Item -Destination $dest -Recurse -ErrorAction SilentlyContinue
Get-ChildItem -Path $folder | Where-Object{$_.Name -notin $excludes} | Where-Object{$_.FullName -like 'Downloads'} | Copy-Item -Destination $dest -Recurse -ErrorAction SilentlyContinue
Get-ChildItem -Path $folder | Where-Object{$_.Name -notin $excludes} | Where-Object{$_.FullName -like 'Outlook'} | Copy-Item -Destination $dest -Recurse -ErrorAction SilentlyContinue
Get-ChildItem -Path $folder | Where-Object{$_.Name -notin $excludes} | Where-Object{$_.FullName -like 'INetCache'} | Copy-Item -Destination $dest -Recurse -ErrorAction SilentlyContinue
Get-ChildItem -Path $folder | Where-Object{$_.Name -notin $excludes} | Where-Object{$_.FullName -like 'Attachments'} | Copy-Item -Destination $dest -Recurse -ErrorAction SilentlyContinue
}
How can I either parse the results.txt file and extract just the file paths to another text file?
Or how can I modify my CopyResults.ps1 script to interpret the file path correctly?
Your issue is that Get-Content converts every line of a file into an element of an array, and you don't want to look at every line. You want to look at a specific part of a specific line that repeats in a specific pattern.
Because of all of these constants, we can make a pretty simple script to get the information you want.
$paths = Get-Content C:\temp\paths.txt
for ($i = 1; $i -lt $paths.Length; $i += 3) {
$path = "$($paths[$i].Split('.txt')[0]).txt"
$path # \\SERVER\PATH\FILE.TXT
}
Your file has a pattern of [empty line, path line, company line]. If we think about the .txt file as an array with many subarrays of that pattern, we can see that we want to get the 1st index ($i = 1) for every set of 3 ($i += 3) in the file.
Since I split by .txt, I have to string interpolate the .txt extension back on.
Edit:
Here's the script modified for your issues
$paths = Get-Content C:\temp\paths.txt
for ($i = 1; $i -lt $paths.Length; $i += 3) {
$pathSplit = $paths[$i].Split('.')
$extension = $pathSplit[1].split(' ')[0]
$path = "${$pathSplit[0]).$extension"
$path # \\SERVER\PATH\FILE.TXT
}
$pathSplit is split at the extension into 2 parts. One is the majority of the path and the other is the rest of the line.
$extension looks at the 1st index and splits by the space in order to isolate the file extension.
Finally, $path combines $pathSplit[0] and $extension to give you the full file path.

One Drive Migration

I'm trying to migrate a file server to One Drive the problem is that there are many folders and files with special characters like '|' , '<' etc..
And also for some reason that company used '*' for versioning like :
'*' => v.1
'**' => v.2
etc...
So I wrote a Powershell script that renames all the files and folders that include a special characters.
#Files
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace(":","-")}
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("<","-")}
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace(">","-")}
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("?","-")}
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("/","-")}
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("|","-")}
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("**********","10")}
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("*********","9")}
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("********","8")}
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("*******","7")}
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("******","6")}
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("*****","5")}
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("****","4")}
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("***","3")}
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("**","2")}
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("*","1")}
#Directories
Get-ChildItem -Directory -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace(":","-")}
Get-ChildItem -Directory -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("<","-")}
Get-ChildItem -Directory -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace(">","-")}
Get-ChildItem -Directory -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("?","-")}
Get-ChildItem -Directory -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("/","-")}
Get-ChildItem -Directory -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("|","-")}
Get-ChildItem -Directory -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("**********","10")}
Get-ChildItem -Directory -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("*********","9")}
Get-ChildItem -Directory -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("********","8")}
Get-ChildItem -Directory -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("*******","7")}
Get-ChildItem -Directory -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("******","6")}
Get-ChildItem -Directory -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("*****","5")}
Get-ChildItem -Directory -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("****","4")}
Get-ChildItem -Directory -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("***","3")}
Get-ChildItem -Directory -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("**","2")}
Get-ChildItem -Directory -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("*","1")}
So this works fine but for some reason I'm getting many error when renaming the folders even though it worked...
Error:
… curse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("*", …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Source and destination path must be different.
I also tried what this thread suggest but this didn't work for me:
Does anyone have a clue why I'm getting this error? And How to fix it?
Thanks in advance.
The error is normal, if you dont have reasons to rename the directory or file because the filename is ok and no character to modify, the name stays same, so an error is displayed saying you cant rename a file/directory with same name.
Despite this error, the process is not stopped and has no impact on the final result
i suggest you to set all couple key to search , new value in an object:
#add your template to replace
$r = #{
":" = "-"
"<" = "-"
"**********" = "10";
"*********" = "9";
"********" = "8"
}
$Folders=Get-ChildItem -Directory -path -Recurse
foreach($folder in $Folders){
$newname = $folder.Name
foreach($k in $r.Keys){
$newname = $newname.Replace($k,$r.$k)
}
#rename only if original name and newname are differents
if(!$newname.equals($folder.Name)){
Rename-Item -Path $folder.FullName -NewName $newname
}
}
do samething for your files......
On my Windows machine I cannot have characters like that in a file or folder name, but theoretically this should work:
(Get-ChildItem -File) | ForEach-Object {
$_ | Rename-Item -NewName {
# replace the '*' by the number of consecutive asterikses found
$name = $_.Name
$match = ([regex] '(\*+)').Match($name)
while ($match.Success) {
$name = $name -replace [regex]::Escape($match), $match.Length
$match = $match.NextMatch()
}
# finally replace the other special characters by a hyphen
$name -replace '[:<>?/|]', '-'
}
}
the brackets around Get-ChildItem force it to complete the gathering before piping to rename. Otherwise, you may end up trying to rename items that were already processed
You run 10 lines of code to rename only 1 type of match per line but effectively you run the same loop 10 times, in this case your code try to rename all folders every single time, but it errors out when it tries to rename the folder with the same newName , because Rename-Item tries to rename it even if the new name is the same as the old one, it appears to not have a validator for that.
In that case, the "cheapest" solution is to add -ErrorAction SilentlyContinue . But a more sophisticated solution would be to validate your dataset with an if statement or a more appropriate for the case switch .
Update : Regex might not be the easiest thing to deal with, so the example bellow account for the wildcard * character as well.
$allFolders=Get-ChildItem -Directory -Recurse
foreach($folder in $allFolders){
switch -Regex ($folder.Name){
'[:<>?\|]'{
$newName=$folder.Name.replace("[:<>?\|]","-")
Rename-Item -Path $folder.FullName -NewName $newName
}
'\*'{
$regexFormat = '\*'
$matchNumber=[regex]::Matches($folder.Name, $regexFormat) | ForEach-Object { $_.value }
$newName=$folder.Name.replace("*",$matchNumber.Count)
Rename-Item -Path $folder.FullName -NewName $newName
}
}
}
This way, you will run 1 loop instead of 10 and you only try to rename items that matches the pattern you provide, so you will see a significant performance increase compared to the multiline code you are using.

How to execute two commands in Powershell after using Where {(...)}

How can I run both 'rename' command and 'hidden' command after I filtered files using Where condition. Each command runs well on its own when placed first, but the second one is neglected.
Get-ChildItem -Recurse -Force | Where {(
$_.Extension -ne ".mp3" -and
$_.Extension -ne ".wmv" )} |
Set-ItemProperty -Name Attributes -Value "Hidden" -WhatIf |
Rename-Item -NewName {$_.Name -replace $_.Extension, -join($_.Extension, ".notmusic")} -
WhatIf
For-Each:
Get-ChildItem -Recurse -Force | Where {( $_.Extension -ne ".mp3" -and $_.Extension -ne ".wmv" )} | ForEach-Object {
$_ | Set-ItemProperty -Name Attributes -Value "Hidden" -WhatIf
$_ | Rename-Item -NewName { $_.Name -replace $_.Extension, -join($_.Extension, ".notmusic")} -WhatIf
}
I use ForEach-Object
Get-ChildItem -Recurse -Force | Where {( $_.Extension -ne ".mp3" -and $_.Extension -ne ".wmv" )} | ForEach-Object {
Set-ItemProperty -Name Attributes -Value "Hidden" -Path $_.FullName -WhatIf
Rename-Item -Path $_.FullName -NewName "$($_.Name).notmusic" -WhatIf
}
I add in Set-ItemProperty -Path
Set-ItemProperty -Name Attributes -Value "Hidden" -Path $_.FullName -WhatIf
i change Rename-Item -Path and -NewName
Rename-Item -Path $_.FullName -NewName "$($_.Name).notmusic"
I see the first couple stanzas grab files in your directory that are not mp3 or wmv files.
Get-ChildItem -Recurse -Force | Where {($_.Extension -ne ".mp3" -and $_.Extension -ne ".wmv")}
That part is good. But since you're taking two actions on each file, you should then pipe to a ForEach-Object in the form of
... | ForEach-Object {
# do something
# do something else
}
And since you're not piping directly into the Set-ItemProperty cmdlet, you'll pass it the path of the filename as such:
Set-ItemProperty -Path $_.FullName -Name Attributes -Value "Hidden"
Then we get to your rename command. Looks like you want the name to be filename.notmusic. Again, we'll need to pass the file path to the cmdlet. Also note how using subexpressions is a clean/nice way of defining the new name
Rename-Item -Path $_.FullName -NewName "$($_.BaseName).notmusic"
Ok so put it all together and we have:
Get-ChildItem -Recurse -Force | Where-Object {($_.Extension -ne ".mp3" -and $_.Extension -ne ".wmv")} | ForEach-Object {
Set-ItemProperty -Path $_.FullName -Name Attributes -Value "Hidden" -WhatIf
Rename-Item -Path $_.FullName -NewName "$($_.BaseName).notmusic" -WhatIf
}

Creating zero-byte file for every file and appending an extension to the end

I want this to recurse every directory and create a zero-byte file for every file using the same name as the file with the extension .xxx added. I was thinking New-Item would be good to use here but I cant seem to get it working right.
Here is what I've tried with no success in PS version 2:
$drivesArray = Get-PSDrive -PSProvider 'FileSystem' | select -Expand Root
foreach ($drive in $drivesArray) {
ls "$drive" | where {
$_.FullName -notlike "${Env:WinDir}*" -and
$_.FullName -notlike "${Env:ProgramFiles}*"
} | ls -ErrorAction SilentlyContinue -recurse | where {
-not $_.PSIsContainer -and
$_.Extension -notmatch '\.xxx|\.exe|\.html'
} | New-Item -Path { $_.BaseName } -Name ($_.FullName+".xxx") -Type File -Force
}
This errors out with
A positional parameter cannot be found that accepts argument "+xxx".
You need to wrap both the second Get-ChildItem (ls) and the New-Item in ForEach-Object statements. Also, do not pass $_.Basename as the path to New-Item. Do it either like this:
New-Item -Path ($_.FullName + '.xxx') -Type File -Force
or like this:
New-Item -Path $_.Directory -Name ($_.Name + '.xxx') -Type File -Force
Modified code:
foreach ($drive in $drivesArray) {
Get-ChildItem $drive | Where-Object {
$_.FullName -notlike "${Env:WinDir}*" -and
$_.FullName -notlike "${Env:ProgramFiles}*"
} | ForEach-Object {
Get-ChildItem $_.FullName -Recurse -ErrorAction SilentlyContinue
} | Where-Object {
-not $_.PSIsContainer -and
$_.Extension -notmatch '^\.(xxx|exe|html)$'
} | ForEach-Object {
New-Item -Path ($_.FullName + '.xxx') -Type File -Force
}
}