"$_.extension -eq" not working as intended? - powershell

I tried to write a small script to automate the creation of playlists (m3u) for dozens of folders/subfolders of mp3/mp4 files, while omitting various other misc files therein. I know very little about Powershell but managed to piece together something that almost works. The only blip is that when I use "$_.extension -eq", it doesn't seem to work, or at least I'm not using it right. If I use it to match log/txt files in a temp folder for example, it works, but not in this instance. Here is the code -
$pathname = read-host "Enter path"
$root = Get-ChildItem $pathname | ? {$_.PSIsContainer}
$rootpath = $pathname.substring(0,2)
Set-Location $rootpath
Set-Location $pathname
foreach($folder in $root) {
Set-Location $folder
foreach($file in $folder) {
$txtfile =".m3u"
$files = gci | Where-Object {$_.extension -eq ".mp3" -or ".mp4"}
$count = $files.count
if($count -ge 2){
$txtfile = "_" + $folder.name + $txtfile
Add-Content $txtFile $files
}
}
if(test-path $txtFile){
Add-Content $txtFile `r
}
Set-Location $pathname
}
I have tried several variations like swapping "-match" for "-eq" but no luck. incidentally, if I omit the "-or ".mp4"" from the parentheses then it works fine, but I need it to match both, and only both mp3/mp4.
Thanks in advance.

As far as you complain about extension, let's start with it. Presumably there is a bug in the code; this expression/syntax is technically valid:
$_.extension -eq ".mp3" -or ".mp4"
But apparently the intent was:
$_.extension -eq ".mp3" -or $_.extension -eq ".mp4"
Try the corrected expression at first.

I'm going to add this as a shortcut option:
gci | Where-Object {".mp3",".mp4" -eq $_.extension}

Related

Filter and delete files and folders(and files inside of folders) older than x days in powershell

this is my first post on this forum. Im a beginner in coding and I need help with one of my very first self coded tools.
I made a small script, which deletes files based on if they are older than date x (lastwritetime). Now to my problem: I want the script also to check for files inside of folders inside of a directory and only delete a folder afterwards if it is truly empty. I cant figure out how to solve the recursion in this problem, seems like the script deletes just the entire folder in relation to the date x. Could anyone tell me please what I missed in this code and help me to create a own recursion to solve the problem or fix the code? Thanks to you all, guys! Here is my code:
I would be glad if someone knows how to make the code work by using a function
$path = Read-Host "please enter your path"
"
"
$timedel = Read-Host "Enter days in the past (e.g -12)"
$dateedit = (Get-Date).AddDays($timedel)
"
"
Get-ChildItem $path -File -Recurse | foreach{ if ($_.LastWriteTime -and !$_.LastAccessTimeUtc -le $dateedit) {
Write-Output "older as $timedel days: ($_)" } }
"
"
pause
Get-ChildItem -Path $path -Force -Recurse | Where-Object { $_.PsisContainer -and $_.LastWriteTime -le $dateedit } | Remove-Item -Force -Recurse
""
Write-Output "Files deleted"
param(
[IO.DirectoryInfo]$targetTolder = "d:\tmp",
[DateTime]$dateTimeX = "2020-11-15 00:00:00"
)
Get-ChildItem $targetTolder -Directory -Recurse | Sort-Object {$_.FullName} -Descending | ForEach-Object {
Get-ChildItem $_ -File | Where-Object {$_.LastWriteTime -lt $dateTimeX} | Remove-Item -Force
if ((Get-ChildItem $_).Count -eq 0){Remove-Item $_ -Force}
}
remove -WhatIf after test
To also remove folders that are older than the set days in the past if they are empty leaves you with the problem that as soon as a file is removed from such a folder, the LastWriteTime of the folder is set to that moment in time.
This means you should get a list of older folders first, before you start deleting older files and use that list afterwards to also remove these folders if they are empty.
Also, a minimal check on user input from Read-Host should be done. (i.e. the path must exist and the number of days must be convertable to an integer number. For the latter I chose to simply cast it to [int] because if that fails, the code would generate an execption anyway.
Try something like
$path = Read-Host "please enter your path"
# test the user input
if (-not (Test-Path -Path $path -PathType Container)) {
Write-Error "The path $path does not exist!"
}
else {
$timedel = Read-Host "Enter days in the past (e.g -12)"
# convert to int and make sure it is a negative value
$timedel = -[Math]::Abs([int]$timedel)
$dateedit = (Get-Date).AddDays($timedel).Date # .Date sets this date to midnight (00:00:00)
# get a list of all folders (FullNames only)that have a LastWriteTime older than the set date.
# we check this list later to see if any of the folders are empty and if so, delete them.
$folders = (Get-ChildItem -Path $path -Directory -Recurse | Where-Object { $_.LastWriteTime -le $dateedit }).FullName
# get a list of files to remove
Get-ChildItem -Path $path -File -Recurse | Where-Object { $_.LastWriteTime -le $dateedit} | ForEach-Object {
Write-Host "older as $timedel days: $($_.FullName)"
$_ | Remove-Item -Force -WhatIf # see below about the -WhatIf safety switch
}
# now that old files are gone, test the folder list we got earlier and remove any if empty
$folders | ForEach-Object {
if ((Get-ChildItem -Path $_ -Force).Count -eq 0) {
Write-Host "Deleting empty folder: $_"
$_ | Remove-Item -Force -WhatIf # see below about the -WhatIf safety switch
}
}
Write-Host "All Done!" -ForegroundColor Green
}
The -WhatIf switch used on Remove-Item is there for your own safety. With that, no file or folder is actually deleted, instead in the console it is written what would be deleted. If you are satisfied that this is all good, remove the -WhatIf and run the code again to really delete the files and folders
try something like this:
$timedel=-12
#remove old files
Get-ChildItem "C:\temp" -Recurse -File | Where LastWriteTime -lt (Get-Date).AddDays($timedel) | Remove-Item -Force
#remove directory without file
Get-ChildItem "C:\temp\" -Recurse -Directory | where {(Get-ChildItem $_.FullName -Recurse -File).count -eq 0} | Remove-Item -Force -recurse

PowerShell Script to add date to folder name in list of home directories saved in variable

I am a PowerShell newbie. I have a .csv file of users that I pulled a list of home directories for using the following:
$hdirpath = Get-Content C:\Temp\UserList.csv | ForEach {Get-ADUser $_ -properties HomeDirectory | Select HomeDirectory}
Output example of the above looks something like this:
HomeDirectory
\servername\Users\roc03\username
\servername\Users\nov01\username
\servername\Users\roc05\username
Now I want to check if a folder exists in each users path and if it exists, i want to add today's date to the end of that folder name. I know there's a If-Exist-Then command that I might be able to use but I'm not sure how to use it with the information saved in $hdirpath.
Any help would be greatly appreciated. Thanks in advance.
Newbie myself so please forgive any mistakes.
From the top of my head I've got this in mind:
foreach ($path in $hdirpath) {
$folders = Get-ChildItem $_ -Recurse | Where-Object {$_.PSIsContainer -eq $True} | Select-Object FullName
$folders | if ($_.Name -eq "Folder Name") {Rename-Item "Folder Name" + Get-Date}
}
Someone will probably correct me, but if they don't give it a go and let me know if you have any problems.
Also - just for future reference, make sure you include code you have tried before posting a question.
EDIT
So, I've now got the following:
$hdirpath = Get-Content C:\temp\dir.txt
$fullDate = Get-Date
$date = $($fullDate.ToShortDateString())
$day = $date -replace "/", "."
foreach ($path in $hdirpath) {
Get-ChildItem -Path $_ -Recurse | Where-Object {$_.PSIsContainer -eq $true} |
Rename-Item -NewName {if ($_.Name.StartsWith('target') -and $_.Name.EndsWith('folder')) {$_.Name + " " + $($day)}}
}
This returns multiple errors saying that it Cannot evaluate parameter 'NewName' because its argument input did not produce any output., but it works and appends the folder name with the current date.
Line 3-5 get the date and format it with "." rather than "\". You can format this however works for you obviously.
My test structure was "C:\temp\roc6\username1" - with multiple subfolders and multiple 'usernameX'. My test folder to re-name was called "targetfolder". End result ended with "targetfolder 08.03.2017".
Revision
This would be my final run of the script - with my limited knowledge this is the best I can do.
$ErrorActionPreference = "SilentlyContinue"
$hdirpath = Get-Content C:\temp\dir.txt
$fullDate = Get-Date
$day = $($fullDate.ToShortDateString()) -replace "/", "."
foreach ($path in $hdirpath) {
Get-ChildItem -Path $_ -Recurse | Where-Object {$_.PSIsContainer -eq $true} |
Rename-Item -NewName {if ($_.Name.StartsWith('target') -and $_.Name.EndsWith('folder')) {$_.Name + " " + $($day)}}
}

Powershell - Test-path looking for 3 files

I am trying to create a powershell script that will check for 3 specific files in a folder. If the 3 files exist continue.
I keep trying to use test-path command. The closest i got was this:
$checkwim = test-path $imagepath\* -include OS.wim, data.wim, backup.wim.
This however does't work for me as it returns "True" if any of the 3 are found. I need to make sure all 3 exist.
I got it to work using the below but i was hoping for an easier\shorter method.
$checkwimos = test-path $imagepath\* -include OS.wim
$checkwimdata = test-path $imagepath\* -include Data.wim
$checkwimonline = test-path $imagepath\* -include Online.wim
if (($checkwimos -ne $True) -or ($checkwimdata -ne $True) -or ($checkwimonline -ne $True))
{
Echo "WIM file(s) not located. Script Aborting"
exit
}
Is there a simpler way to do this?
if you wanted to avoid hard coding a check for each file type to include you could do something like this:
$files = #('os.wim','data.wim','backup.wim')
$checkWim = $files | foreach-object {test-path $imagepath\* -Include $_} | Where-Object {$_ -eq $false}
If($checkWim -eq $false){"WIM file(s) not located. Script Aborting"}
else{
#do stuff
}
You could also import a list of file instead of creating an array.
Another tack:
$files = #('os.wim','data.wim','backup.wim')
if (($files | foreach {test-path $imagepath\$_}) -contains $false)
{
Echo "WIM file(s) not located. Script Aborting"
exit
}
Your way is already pretty simple, although you could make it a bit more readable with:
$checkwimos = test-path (Join-Path $imagepath OS.wim)
$checkwimdata = test-path (Join-Path $imagepath Data.wim)
$checkwimonline = test-path (Join-Path $imagepath Online.wim)
if (-not ($checkwimos -and $checkwimdata -and $checkwimonline))
{
Echo "WIM file(s) not located. Script Aborting"
exit
}
if you insist on a one-liner, the below should work.
if (#(Get-ChildItem -LiteralPath "C:\temp\" | Where-Object -FilterScript {#("os.wim", "data.wim", "backup.wim") -ccontains $_.Name}).Count -eq 3)
{
write "Files were present"
}

Renaming a file that has a bracket in the file name using power shell

I am trying to rename a file that has a bracket in the file name. This does not seem to work because powershell sees [] as special characters and does not know what to do.
I have a folder on my computer c:\test. I want to be able to look through that folder and rename all files or portions of the file. The following code seems to work but if the file has any special characters in it the code fails:
Function RenameFiles($FilesToRename,$OldName,$NewName){
$FileListArray = #()
Foreach($file in Get-ChildItem $FilesToRename -Force -Recurse | Where-Object {$_.attributes -notlike "Directory"})
{
$FileListArray += ,#($file)
}
Foreach($File in $FileListArray)
{
IF ($File -match $OldName )
{
$File | rename-item -newName {$_ -replace "$OldName", "$NewName" }
}
}
}
renamefiles -FilesToRename "c:\test" -OldName "testt2bt" -NewName "test"
I did find a similar question: Replace square bracket using Powershell, but I can't understand how to use the answer cause it's just a link explaining the bug:
For multiple files this can be done with one line.
To remove the bracket you should try:
get-childitem | ForEach-Object { Move-Item -LiteralPath $_.name $_.name.Replace("[","")}
Move-Item -literalpath "D:\[Copy].log" -destination "D:\WithoutBracket.txt"
Use the literalpath switch with the Move-Item cmdlet [instead of using the rename-item cmdlet]
As far as bracket are concerned, you've got Microsoft official answer in an old Technet Windows PowerShell Tip of the Week.
You can use :
Get-ChildItem 'c:\test\``[*``].*'
Thanks for help guys you all helped a lot this is the solution I came up with in the end after reading your reply’s .
I have a folder on my pc called c:\test and it has a file in it called "[abc] testfile [xas].txt" and i want it to be called testfile2.txt
Function RenameFiles($FilesToRename,$OldName,$NewName){
$FileListArray = #()
Foreach($file in Get-ChildItem $FilesToRename -Force -Recurse | Where-Object {$_.attributes -notlike "Directory"})
{
$FileListArray += ,#($file.name,$file.fullname)
}
Foreach($File in $FileListArray)
{
IF ($File -match $OldName )
{
$FileName = $File[0]
$FilePath = $File[1]
$SName = $File[0] -replace "[^\w\.#-]", " "
$SName = $SName -creplace '(?m)(?:[ \t]*(\.)|^[ \t]+)[ \t]*', '$1'
$NewDestination = $FilePath.Substring(0,$FilePath.Length -$FileName.Length)
$NewNameDestination = "$NewDestination$SName"
$NewNameDestination | Write-Host
Move-Item -LiteralPath $file[1] -Destination $NewNameDestination
$NewNameDestination | rename-item -newName {$_ -replace "$OldName", "$NewName" }
}
}
}
renamefiles -FilesToRename "c:\test" -OldName "testfile" -NewName "testfile2"

Compress files in folder to zip file by using PS

I have the following scripts to compress a folder (all files in the folder) to a zip file:
set-content $zipFileName ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
$ZipFile = (new-object -com shell.application).NameSpace($zipFileName)
Get-ChildItem $folder | foreach {$zipFile.CopyHere($_.fullname)}
where $folder = "C:\Test", and $zipFileName = "C:\data\test.zip" as example
It works fine if "C:\Test" contains no empty sub-folders, and it seems works recursively to compress all files within sub-folders. I really like above simple line script. For example:
C:\Test
file1.dat
file2.dat
Test-Sub
File21.bat
....
However, I got error in one case. I find that if there is any empty folder such as "C:\Test\EmptySub",
C:\Test
file1.dat
file2.dat
Test-Sub
File21.bat
....
EmptySub
AnotherSub
file31.sp1
...
the script will generate an error. I tried the following scripts:
Get-ChildItem $files -Recurse | foreach { if (!$_.PSIsContainer)
{$zipFile.CopyHere($_.fullname)}}
This does not work as expected. It just skips all the sub-folders. Not sure if there are filter or clause available to skip all the empty sub-folders?
Updated: Based on suggests, I gave it a try. My problem has not be resolved. Here is the update of my question. First, I updated the scripts above to show how $zipFile object is created. Secondly I have the suggested codes:
Get-ChildItem $files | ? {-not ($_.PSIsContainer -eq $True -and
$_.GetFiles().Count -eq 0) } | % {$zipfile.CopyHere($_.fullname)}
I tried above updates on my WindowsXP, it works fine with empty sub-folders. However, the same codes do not workin in Windows 2003 Server. The following is the error message:
[Window Title]
Compressed (zipped) Folders Error
[Content]
File not found or no read permission.
[OK]
Not sure if this type PK object works in Windows 2003 server, or if there is other settings for the object.
You can detect empty directories by testing against an empty return from get-childitem. For example, this will return the list of empty directories
dir | where{$_.PSIsContainer -and -not (gci $_)}
Though in your case, you want the inverse:
Get-ChildItem $files | where{-not ($_.PSIsContainer -and -not (gci $_))} | foreach {$zipfile.CopyHere($_.fullname)}}
Your code works recursively in the sense that you get any kind of child item (folders included). If your intention is to exclude empty folder you should filter them:
Try this one liner:
gci $folder |`
? {-not ($_.PSIsContainer -eq $True -and $_.GetFiles().Count -eq 0) } |`
% {$zipfile.CopyHere($_.fullname)}
Here is the function I created to zip files. The reason why you are getting the read error is because .CopyHere is an asynchronous copy process, so my script verifies the file exists in the zip file before continuing to the next file to zip:
function Out-ZipFile ([string]$path)
{
try
{
$files = $input
if ($path.StartsWith(".\")) { $path=$path.replace(".\","$($pwd)\") }
if (-not $path.Contains("\")) { $path="$($pwd)\$($path)" }
if (-not $path.EndsWith('.zip')) {$path += '.zip'}
if (-not (test-path $path)) {
set-content $path ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
}
$ZipFile = (new-object -com shell.application).NameSpace($path)
$files | % {
$FileName = $_.name
"Adding $FileName to $path";
$ZipFile.CopyHere($_.fullname);
While (($ZipFile.Items() | where { $_.Name -like $FileName }).Size -lt 1) { Start-Sleep -m 100 };
}
}
catch
{
write-output "Error Encountered: `n$_"
return $_
throw
}
}