PowerShell: Sort-Object descending wrong order - windows

We currently have the following folder structure:
C:\Packages\Adobe\DC\9.2.2
C:\Packages\Adobe\DC\10.0.3
C:\Packages\Adobe\DC\10.0.8
C:\Packages\Microsoft\Edge\6.1.10
C:\Packages\Microsoft\Edge\6.1.18
C:\Packages\Microsoft\Edge\6.1.20
With a PowerShell script I try to keep only the highest version of the respective application folder and delete all others. The result should be:
C:\Packages\Adobe\DC\10.0.8
C:\Packages\Microsoft\Edge\6.1.20
However, the sorting doesn't seem to work properly in my script.
$folders_root = Get-ChildItem -Path C:\Packages -Directory
foreach ($folder in $folders_root)
{
$folders_appilcation = Get-ChildItem $folder.FullName -Directory
foreach ($app_folder in $folders_appilcation)
{
$versionfolder = Get-ChildItem $app_folder.FullName -Directory | Sort-Object -Descending | Select-Object -Skip 1 | % {Write-host "Deleting $($_.FullName)"<#; Remove-Item $_.FullName#>}
}
}
For the path "C: \ Packages \ Adobe \ DC" 9.2.2 is considered as the highest version (according to the script) but it should be 10.0.8.
Deleting C:\Packages\Adobe\DC\10.0.8
Deleting C:\Packages\Adobe\DC\10.0.3
Deleting C:\Packages\Microsoft\Edge\6.1.18
Deleting C:\Packages\Microsoft\Edge\6.1.10
Can someone tell me what I'm doing wrong?

Sort is comparing strings and not numbers. When you compare the string 10 and 9 it will interpret the 10 as a 1.
Force the sort to us it as version, try something like that:
sort {[version]::Parse(($_.split("\"))[-1])} -Descending

Thank you. The following code works perfectly:
$folders_root = Get-ChildItem -Path \\hkt-cmgmt01a\d$\Empirum\Configurator\Packages -Directory
foreach ($folder in $folders_root)
{
$folders_appilcation = Get-ChildItem $folder.FullName -Directory
foreach ($app_folder in $folders_appilcation)
{
$versionfolder = Get-ChildItem $app_folder.FullName -Directory | Sort-Object { [version] $_.Name } -Descending -ErrorAction SilentlyContinue| Select-Object -Skip 1 | % {Write-host "Deleting $($_.FullName)"<#; Remove-Item $_.FullName#>}
}
}

Related

Getting root folder name with PS

I am trying to create a PowerShell script to fetch the root folder's name where in their subdirectories files with error names are present with today's date. Below is the sample code I have tried so far to pick the folder names.
Root Log folder - C:\Errorlogs, contains many other application log level folders.
$targetDir="C:\Errorlogs"
Get-ChildItem $targetDir -Recurse -ErrorAction SilentlyContinue -Force -Filter "*Error*"|
where {([datetime]::now.Date -eq $_.lastwritetime.Date)} |
select FullName
I have tried the above code; however, it's giving me the whole path as result, whereas I only need the folder name.
Result - C:\Errorlogs\AsyncCreateUsersAPIProcessor\202302\04\Error.txt
Required - AsyncCreateUsersAPIProcessor
Use string LastIndexOf and SubString
$rootPath = "C:\Temp\Errorlogs"
$date = [DateTime]::Now.ToString("yyyyMM\\\\dd")
$pattern = '\\(?<folder>\w+)\\' + $date + '\\Error.*$'
$files = Get-ChildItem -Path $rootPath -Recurse | Select-Object -Property Fullname | Where-Object {$_.Fullname -Match $pattern}
foreach($file in $files)
{
$file.Fullname -match $pattern
Write-Host "folder = " $Matches.folder
}
Looks like you can do it just with splitting the path using \ as delimiter then picking the 3rd token (2nd index of an array):
$targetDir = "C:\Errorlogs"
Get-ChildItem $targetDir -Recurse -ErrorAction SilentlyContinue -Force -Filter "*Error*" |
Where-Object { [datetime]::Now.Date -eq $_.LastWriteTime.Date } |
Select-Object #{ N='Name'; E={ $_.FullName.Split('\')[2] }}
Another option if you want 2 levels up in the folder hierarchy is to query the .Directory property of the file then the .Parent property of the parent folder (2 times or as many times as needed):
$targetDir = "C:\Errorlogs"
Get-ChildItem $targetDir -Recurse -ErrorAction SilentlyContinue -Force -Filter "*Error*" |
Where-Object { [datetime]::Now.Date -eq $_.LastWriteTime.Date } |
Select-Object #{ N='Name'; E={ $_.Directory.Parent.Parent.Name }}
As long as the subfolders inside the folder you are after all have numeric-only names, you can loop backwards to get at the first non-numeric foldername and output that.
$targetDir = "C:\Errorlogs"
Get-ChildItem -Path $targetDir -File -Filter "*Error*" -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { [datetime]::Now.Date -eq $_.LastWriteTime.Date } | ForEach-Object {
$parentDir = $_.Directory
while ($parentDir.Name -match '^\d+$') { $parentDir = $parentDir.Parent }
$parentDir.Name
}
That way, even a path C:\Errorlogs\AsyncCreateUsersAPIProcessor\202302\02\04\1234\567\Error.txt would produce folder name AsyncCreateUsersAPIProcessor

Matching two lists and find uninstall string with Powershell

I have a problem with my Script.
I wanted to make a Script which makes a list of software which is found in a specific registry path
and see if this software equals installed software. and if so it should output me the uninstall string.
but right now it does not work as wanted. it never show me the output I wanted even if its similar. As Example i have the Program Git as Branding and in the software I got Git version 2.26.2 but it wont output the uninstall string when I selected git.
My code is:
$branding = Get-ChildItem "HKLM:\Software\DLR\Branding" | Get-ItemProperty | Select-Object -expandProperty ProgramName
$software = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall | Get-ItemProperty | Select-Object -ExpandProperty DisplayName
ForEach ($brandinglist in $branding) {
$objCombobox.Items.Add("$brandinglist")
}
$objComboBox_SelectedIndexChanged=
{
$selectme = $objCombobox.SelectedItem
Write-Host $selectme
if ("$selectme" -like "*$software*") {
$uninstall = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall | Get-ItemProperty | Where-Object {$_.DisplayName -match "$electme" } | Select-Object -Property UninstallString
Write-Host "$uninstall"
}
}
You are trying the -like comparison wrong, in which you compare the selected item to an array of displaynames which doesn't work that way.
Also, there is no reason to get the Uninstall strings and Displaynames using an almost identical code twice.
Try
# get a string array of program names
$branding = Get-ChildItem -Path 'HKLM:\Software\DLR\Branding' | Get-ItemProperty | Select-Object -ExpandProperty ProgramName
$regPaths = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall', 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
# get an object array of DisplayName and UninstallStrings
$software = Get-ChildItem -Path $regPaths | Get-ItemProperty | Select-Object DisplayName, UninstallString
# fill the combobox with the (string array) $branding
$objCombobox.Items.AddRange($branding)
$objComboBox.Add_SelectedIndexChanged ({
$selectme = $objCombobox.SelectedItem
Write-Host $selectme
# get the objects that have a displayname like the selected item and write out the matching Uninstall strings
$software | Where-Object {$_.DisplayName -like "*$selectme*" } | ForEach-Object {
Write-Host $_.UninstallString
}
})

Powershell command to fetch all file path for all desired files extensions

I want to search all drives using PowerShell on windows machine to get the list of all files along with their extensions -
Based on desired extension we pass in it like - *.mp3 or
Fetch all files with multiple extensions like - *.txt, *.mp3 etc.
I tried below script but its giving only information from where we are running it. But I want to scan whole machine.
Get-ChildItem -Path .\ -Filter ***.doc** -Recurse -File| Sort-Object Length -Descending | ForEach-Object { $_.BaseName }
Checkout the Get-PSDrive cmdlet. It returns a list of drives, and you can specify just disk drives with the -PSProvider FileSystem parameter:
foreach ( $drive in $(Get-PSDrive -PSProvider FileSystem) ) {
Get-ChildItem -Path $drive.Root -Filter ***.doc** -Recurse -File |
Sort-Object Length -Descending |
ForEach-Object { $_.BaseName }
}
Didn't test that but you get the idea.
Using -Include on Get-ChildItem will allow you to specify a list of extensions. The -ErrorAction will cause it to skip drives that are not available such as an unmounted CD drive.
Get-PSDrive -PSProvider FileSystem |
ForEach-Object {
Get-ChildItem -Path $_.Root -Recurse -Include '*.doc*', '*.txt' -ErrorAction SilentlyContinue |
ForEach-Object { $_.Name }
} |
ForEach-Object {[PSCustomObject]#{HashCode = $_.GetHashCode(); FullName = $_.FullName}}
} |
Export-Csv -Path $TempFile -NoTypeInformation -Encoding ASCII
Update:
Here is a better way. It will prevent unknown extensions from getting into the mix such as "Microsoft.NET.Sdk.Publish.Docker.targets."
$ExtensionList = #('.txt', '.doc', '.docx', '.mp3')
$TempFile = Join-Path -path $Env:TEMP -ChildPath "$($pid.ToString()).tmp"
Get-PSDrive -PSProvider FileSystem |
ForEach-Object {
Get-ChildItem -Path $_.Root -Recurse -ErrorAction SilentlyContinue |
Where-Object { $ExtensionList -contains $_.Extension } |
ForEach-Object {
[PSCustomObject]#{
HashCode = $_.GetHashCode();
DirectoryName = $_.DirectoryName
Name = $_.Name
}
}
} |
Export-Csv -Path $TempFile -Delimiter ';' -NoTypeInformation -Encoding ASCII
Write-Host "The temp file is $TempFile"
This is more than what the original question asked, but if you are going to go through the trouble of listing all your files, I suggest getting the filehash as well so you can determine if you have duplicates. A simple file name search will not detect if the same file has been saved with a different name. Adding to what #lit (https://stackoverflow.com/users/447901/lit) has posted:
$ExtensionList = #('.txt', '.doc', '.docx', '.mp3')
Get-PSDrive -PSProvider FileSystem |
ForEach-Object {
Get-ChildItem -Path $_.Root -Recurse -ErrorAction SilentlyContinue |
Where-Object { $ExtensionList -eq $_.Extension } |
## ForEach-Object { $_.Name, $_.FullName, $_.GetHashCode() }
Select-Object #{Name="Name";Expression={$_.Name}}, #{Name="Hash";Expression={$_.GetHashCode()}}, #{Name="FullName";Expression={$_.FullName}} |
Export-Csv -Path C:\Temp\testing.csv -NoTypeInformation -Append
}
The addition of the file hash will allow you to see if you have duplicates and the full name will allow you to see where they are located.

Compare mkv's creationtime

I've been tasked with creating a script that checks to see if the office cameras we've set up have stopped uploading their feeds to the "Camera" share located on our Windows 2016 storage server. If the NEWEST .mkv is over an hour old compared to the current time (get-date) then the "problem" camera needs to be restarted manually. (No need to script that part.)
Here's what my Director has written so far:
#Variable Definitions start here
$numhours = 1
Get-ChildItem "d:\Shares\Cameras" | Foreach {
$folderToLookAt = ($_.FullName + "\*.mkv")
$result = Get-ChildItem -Recurse $folderToLookAt | Sort-Object CreationTime -Descending
echo $result[0].FullName
echo $result[0].CreationTime
}
The first variable really isn't used yet, but I'm kind of dumb-struck as what to do next. The above returns the full names and creation times successfully of the newest .mkvs
Suggestions on the next part?
Invert the logic - instead of searching all the files, sorting them, finding the most recent, and checking the date, do it the other way round.
Look for files created since the cutoff, and alert if there were none found:
$cutOffTime = [datetime]::Now.AddHours(-1)
Get-ChildItem "d:\Shares\Cameras" | Foreach {
$folderToLookAt = ($_.FullName + "\*.mkv")
$result = Get-ChildItem -Recurse $folderToLookAt | Where-Object { $_.CreationTime -gt $cuttoffTime }
if (-not $result)
{
"$($_.Name) has no files since the cutoff time"
}
}
I'm assuming your paths look like:
D:\Shares\Cameras\Camera1\file1.mkv
D:\Shares\Cameras\Camera1\file2.mkv
D:\Shares\Cameras\Camera2\file1.mkv
D:\Shares\Cameras\Camera2\file2.mkv
D:\Shares\Cameras\Camera3\file1.mkv
.
.
.
If so, I would do something like this:
# The path to your files
$CameraShareRoot = 'D:\Shares\Cameras';
# Number of Hours
$NumberOfHours = 1;
# Date and time of significance. It's $NumberOfHours in the past.
$MinFileAge = (Get-Date).AddHours( - $NumberOfHours);
# Get all the folders at the camera share root
Get-ChildItem -Path $CameraShareRoot -Directory | ForEach-Object {
# Get the most recently created file in each folder
$_ | Get-ChildItem -Recurse -Filter '*.mkv' -File | Sort-Object -Property CreationTime -Descending | Select-Object -First 1
} | Where-Object {
# Remove any files that were created after our datetime
$_.CreationTime -lt $MinFileAge;
} | Select-Object -Property FullName, CreationTime
This will just output the full file name and creation time for stale cameras.
You could do something like this to email yourself a report when the results have any files:
# The path to your files
$CameraShareRoot = 'D:\Shares\Cameras';
# Number of Hours
$NumberOfHours = 1;
# Date and time of significance. It's $NumberOfHours in the past.
$MinFileAge = (Get-Date).AddHours( - $NumberOfHours);
# Get all the folders at the camera share root, save the results to $StaleCameraFiles
$StaleCameraFiles = Get-ChildItem -Path $CameraShareRoot -Directory | ForEach-Object {
# Get the most recently created file in each folder
$_ | Get-ChildItem -Recurse -Filter '*.mkv' -File | Sort-Object -Property CreationTime -Descending | Select-Object -First 1;
} | Where-Object {
# Remove any files that were created after our datetime
$_.CreationTime -lt $MinFileAge;
}
# If there are any stale camera files
if ($StaleCameraFiles) {
# Send an email
$MailMessage = #{
SmtpServer = 'mail.example.com';
To = 'youremail#example.com';
From = 'youremail#example.com';
Subject = 'Stale Camera Files';
Body = $StaleCameraFiles | Select-Object -Property FullName, CreationTime | ConvertTo-Html -Fragment | Out-String;
BodyAsHtml = $true;
}
Send-MailMessage #MailMessage;
}
Generally you will want to use LastWriteTime instead of CreationTime since the latter can be updated by a file move or copy, but maybe that's what you want here.
You have to compare the CreationTime date with (Get-Date).AddHours(-1). The AddHours method allows you to add hours to the DateTime, but also to subtract.
You can use the following example:
$Path = 'd:\Shares\Cameras'
$CreationTime = Get-ChildItem -Path $Path -Filter *.mkv |
Sort-Object -Property CreationTime -Descending |
Select-Object -First 1 -ExpandProperty CreationTime
if ($CreationTime -lt (Get-Date).AddHours(-1)) {
# your action here (restart, send mail, write output, ...)
}
It also optimizes your code a bit. ;)
$LatestFile = Get-ChildItem C:\Users\Connor\Desktop\ | Sort CreationTime | Select -Last 1
if ($LatestFile.CreationTime -gt (Get-Date).AddHours(-1)){
#It's Currently Working
} else {
#Do Other Stuff
}
try this :
Get-ChildItem "c:\temp" -Filter *.mkv -File | sort CreationTime -Descending |
select -First 1 | where CreationTime -lt (Get-Date).AddHours(-1) |
%{Write-Host "Alert !!" -ForegroundColor Red}

Counting folders with Powershell

Does anybody know a powershell 2.0 command/script to count all folders and subfolders (recursive; no files) in a specific folder ( e.g. the number of all subfolders in C:\folder1\folder2)?
In addition I also need also the number of all "leaf"-folders. in other words, I only want to count folders, which don't have subolders.
In PowerShell 3.0 you can use the Directory switch:
(Get-ChildItem -Path <path> -Directory -Recurse -Force).Count
You can use get-childitem -recurse to get all the files and folders in the current folder.
Pipe that into Where-Object to filter it to only those files that are containers.
$files = get-childitem -Path c:\temp -recurse
$folders = $files | where-object { $_.PSIsContainer }
Write-Host $folders.Count
As a one-liner:
(get-childitem -Path c:\temp -recurse | where-object { $_.PSIsContainer }).Count
To answer the second part of your question, of getting the leaf folder count, just modify the where object clause to add a non-recursive search of each directory, getting only those that return a count of 0:
(dir -rec | where-object{$_.PSIsContainer -and ((dir $_.fullname | where-object{$_.PSIsContainer}).count -eq 0)}).Count
it looks a little cleaner if you can use powershell 3.0:
(dir -rec -directory | where-object{(dir $_.fullname -directory).count -eq 0}).count
Another option:
(ls -force -rec | measure -inp {$_.psiscontainer} -Sum).sum
This is a pretty good starting point:
(gci -force -recurse | where-object { $_.PSIsContainer }).Count
However, I suspect that this will include .zip files in the count. I'll test that and try to post an update...
EDIT: Have confirmed that zip files are not counted as containers. The above should be fine!
Get the path child items with recourse option, pipe it to filter only containers, pipe again to measure item count
((get-childitem -Path $the_path -recurse | where-object { $_.PSIsContainer }) | measure).Count

Resources