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

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
}

Related

Powershell error when deleting old files and folders

I'm trying to delete some old files in an archive folder and my script works fine until it gets to the last section where it removes the empty folders (testing using -whatif initially). I get the following error:
Remove-Item : Cannot bind argument to parameter 'Path' because it is null.
At C:\ArchiveDelete.ps1:13 char:39
+ $dirs | Foreach-Object { Remove-Item <<<< $_.fullname -whatif }
+ CategoryInfo : InvalidData: (:) [Remove-Item], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.RemoveItemCommand
Tried to find a suitable answer on here but can't find a solution (I know I may be using an older version of Powershell)
#Days older than
$HowOld = -900
#Path to the root folder
$Path = "C:\SharedWorkspace\ArchiveDSAgile"
#Deletion files task
get-childitem $Path -recurse | where {$_.lastwritetime -lt (get-date).adddays($HowOld) -and -not $_.psiscontainer} |% {remove-item $_.fullname -force -whatif}
#Deletion empty folders task
do {
$dirs = gci $Path -recurse | Where { (gci $_.fullName -Force).count -eq 0 -and $_.PSIsContainer } | select -expandproperty FullName
$dirs | Foreach-Object { Remove-Item $_ -whatif }
} while ($dirs.count -gt 0)
None of your loops are necessary.
You can feed the output of Where-Object right into Remove-Item
$AgeCap = (Get-Date).AddDays(-900)
$Path = "C:\SharedWorkspace\ArchiveDSAgile"
Get-ChildItem $Path -Recurse -File | Where-Object LastWriteTime -lt $AgeCap | Remove-Item -WhatIf
The -File parameter for Get-ChildItem available in PowerShell 3.0 (I believe) and higher. The Workaround for PowerShell 2.0 would be checking against $_.PSIsContainer in Where-Object, as you did in your sample code.
Finally got it working:
#Days older than
$HowOld = -900
#Path to the root folder
$Path = "C:\SharedWorkspace\ArchiveDSAgile"
#Deletion files task
get-childitem $Path -recurse | where {$_.lastwritetime -lt (get-date).adddays($HowOld) -and -not $_.psiscontainer} |% {remove-item $_.fullname -force -whatif}
#Deletion empty folders task
Get-ChildItem $Path -Recurse | Where-Object {$_.lastwritetime -lt (get-date).adddays($HowOld) -and -not !$_.psiscontainer} | Remove-Item -recurse -WhatIf
Thanks for your help

Doing operations on file names and directories with 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 "}

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
}
}

PowerShell script to remove characters from files and folders

I am trying to recursively remove certain characters from files and folders using a PowerShell script. Below is the script that I have found, but it will only remove underscores from files, not folders. There are a few characters which will need to be removed, but I am fine with having a script for each character if need be. It does recursively change files in the folders, but no folders are 'fixed'.
Current PowerShell script:
'dir -Recurse | where {-not $_.PsIscontainer -AND $_.name -match "_"} | foreach {
$New = $_.name.Replace("_","")
Rename-Item -path $_.Fullname -newname $New -passthru
}'
As pointed out in the comments the core of your issue is that you are excluding folders with the -Not $_.PsIscontainer component of your Where block.
dir -recurse | where {$_.name -match "_"} | ...
The second issue that you are having is most likely that since you are changing folder names the children you had previously inventoried with dir/Get-ChildItem would then have incorrect paths. One way to address this would be to process files first then folders.
$filesandfolders = Get-ChildItem -recurse | Where-Object {$_.name -match "_"}
$filesandfolders | Where-Object {!$_.PsIscontainer} | foreach {
$New=$_.name.Replace("_","")
Rename-Item -path $_.Fullname -newname $New -passthru -WhatIf
}
$filesandfolders | Where-Object {$_.PsIscontainer} | foreach {
$New=$_.name.Replace("_","")
Rename-Item -path $_.Fullname -newname $New -passthru -WhatIf
}
By no means the prettiest solution but it would work. It processes all the files first, then the folders. Remove the -Whatifs when you are sure it would do what you expect
Other characters
You had mentioned there were other characters that you were looking to remove. That wouldn't be a tall order. You could be using regex for this so lets try that.
$characters = "._"
$regex = "[$([regex]::Escape($characters))]"
$filesandfolders = Get-ChildItem -recurse | Where-Object {$_.name -match $regex}
$filesandfolders | Where-Object {!$_.PsIscontainer} | foreach {
$New=$_.name -Replace $regex
Rename-Item -path $_.Fullname -newname $New -passthru -WhatIf
}
$filesandfolders | Where-Object {$_.PsIscontainer} | foreach {
$New=$_.name -Replace $regex
Rename-Item -path $_.Fullname -newname $New -passthru -WhatIf
}
That would remove all of the periods and underscores from those files and folders.
Not tested but you might even be able to get it down to these few lines
$characters = "._"
$regex = "[$([regex]::Escape($characters))]"
$filesandfolders = Get-ChildItem -recurse | Where-Object {$_.name -match $regex}
$filesandfolders | Where-Object {!$_.PsIscontainer} | Rename-Item -NewName ($_.name -Replace $regex) -PassThru -WhatIf
$filesandfolders | Where-Object {$_.PsIscontainer} | Rename-Item -NewName ($_.name -Replace $regex) -PassThru -WhatIf

Rename and then copy is not working

I am trying to rename certain files and then copy them to a backup location as below:
gci $src `
| ?{!$_.psiscontainer -and $_.extension.length -eq 0 -and $_ -match "tmp_\d{1}$"} `
| %{ ren -path $_.fullname -new ($_.name + ".ext") } `
| %{ cpi -path $_.fullname -dest $bkup -force}
The renaming part is working fine. But the renamed files are not being copied over to the backup location. What I am doing wrong here?
Rename-Item doesn't return anything so there is nothing to pipe to Copy-Item. You could just put both commands in the for each block together:
gci $src `
| ?{!$_.psiscontainer -and $_.extension.length -eq 0 -and $_ -match "tmp_\d{1}$"} `
| %{ $renamedPath = $_.FullName + ".ext"; `
ren -path $_.FullName -new $renamedPath; `
cpi -path $renamedPath -dest $bkup -force }
By default renamed items will not be pushed back onto the pipeline, use the -PassThru switch to pass them on:
gci $src `
| ?{!$_.psiscontainer -and $_.extension.length -eq 0 -and $_ -match "tmp_\d{1}$"} `
| %{ ren -path $_.fullname -new ($_.name + ".ext") -PassThru } `
| %{ cpi -path $_.fullname -dest $bkup -force}
You accomplish both in one operation with move-item.
gci $src
| ?{!$_.psiscontainer -and $_.extension.length -eq 0 -and $_ -match "tmp_\d{1}$"}
| %{
$newname = $_.Name + ".ext"
move-item -path $_.FullName -dest "$bkup\$newname"
}
One liner:
gci $src | ?{!$_.psiscontainer -and !$_.extension -and $_ -match 'tmp_\d$'} | move-item -dest {"$bkup\$($_.Name + '.ext')"}