Better way of exporting the output in columns rather than rows using Export-Csv method in PowerShell for file hash comparison in Excel side by side - powershell-4.0

I have two folders (Fold1 and Fold2) with a lots of files in different formats (.doc, .pdf, .xml, .html) in them, and total number of items (files) may vary in each folder. I am trying to compare the MD5 checksums for each file present in both the folders and for the files that are extra in either folders just list their checksums, so that I can copy/check those files later.
This is where I have reached:
$path1 = "C:\Users\username\Desktop\Fold1"
$path2 = "C:\Users\username\Desktop\Fold2"
$Data1 = dir "C:\Users\username\Desktop\Fold1" -Recurse |
Where-Object {!$_.PSIsContainer} |
Get-FileHash -Algorithm MD5 |
select #{n='Hash';e={$_.Hash}}, #{n='File';e={Split-Path $_.Path -Leaf}}
$Data2 = dir "C:\Users\username\Desktop\Fold2" -Recurse |
Where-Object {!$_.PSIsContainer} |
Get-FileHash -Algorithm MD5 |
select #{n='Hash';e={$_.Hash}}, #{n='File';e={Split-Path $_.Path -Leaf}}
$Full = foreach ($d in $Data1) {
$d | Select-Object Hash, File,#{n="Hash2";e={
($Data2 | Where-Object File -eq $d.File).Hash
}}
}
$Full | Export-Csv .\report.csv
This gives output like this:
Problem 1:
Since I am using Where-Object File -eq $d.File, so just in case if Folder 2 has one extra file than Folder 1, in such scenario it won't capture the extra file and the comparison goes wrong because I need to capture all the items regardless. To escape this I tried adding .Count, which looks like this:
$Data1Count = (Get-ChildItem -File -Path $path1).Count
$Data2Count = (Get-ChildItem -File -Path $path2).Count
if ($Data1Count -gt $Data2Count) {
$Full = foreach ($d in $Data1) {
$d | Select-Object Hash,File,#{n="Hash2";e={
($Data2 | Where-Object File -eq $d.File).Hash
}}
}
$Full
} elseif ($Data2Count -gt $Data1Count) {
$Full = foreach ($d in $Data2) {
$d | Select-Object Hash,File,#{n="Hash2";e={
($Data1 | Where-Object File -eq $d.File).Hash
}}
}
$Full
} else {
$Full = foreach ($d in $Data1) {
$d | Select-Object Hash,File,#{n="Hash2";e={
($Data2 | Where-Object File -eq $d.File).Hash
}}
}
$Full
}
Problem 2:
This method lists all the items (files) from one of the folders (Folder1 or Folder2) whichever has higher file counts, but it still does not list the extra files in the other folder. Just to show you, here is the directory structure:
and this is the output (since number of files in Folder 2 is > Folder 1):
What I am trying to achieve is something like the following as the output of comparison, which lists all the matching files as well as extra files in either folders:

What you're trying to do isn't how you'd handle something like this in PowerShell. The appropriate way of tackling this would be enumerating the files like you do in your first code sample, and then compare the lists with Compare-Object:
Compare-Object $Data1 $Data2 -Property Hash -IncludeEqual -PassThru
which would give you output like this:
Hash File SideIndicator
---- ---- -------------
1DA53AC45042DB9413D1A6F055F7C5BA a.docx ==
2E9D15DF495521763D6CD5090B7DEF48 b.pdf ==
8DA3E12E4908F49055BD679D68848D5A c.pdf =>
6F0D8230A93276D335CE656CEB54B764 d.doc <=
You can then use the side indicator for transforming the data like this:
Compare-Object $Data1 $Data2 -IncludeEqual -Property Hash -PassThru |
Select-Object #{n='Hash';e={if ($_.SideIndicator -ne '=>') {$_.Hash}}},
#{n='File';e={if ($_.SideIndicator -ne '=>') {$_.File}}},
#{n='Hash2';e={if ($_.SideIndicator -ne '<=') {$_.Hash}}},
#{n='File2';e={if ($_.SideIndicator -ne '<=') {$_.File}}}
which would produce output like this:
Hash File Hash2 File2
---- ---- ----- -----
1DA53AC45042DB9413D1A6F055F7C5BA a.docx 1DA53AC45042DB9413D1A6F055F7C5BA a.docx
2E9D15DF495521763D6CD5090B7DEF48 b.pdf 2E9D15DF495521763D6CD5090B7DEF48 b.pdf
8DA3E12E4908F49055BD679D68848D5A c.pdf
6F0D8230A93276D335CE656CEB54B764 d.doc
or transform the data like this:
Compare-Object $Data1 $Data2 -IncludeEqual -Property Hash -PassThru |
Select-Object Hash, File,
#{n=$path1;e={if ($_.SideIndicator -ne '=>') {'X'}}},
#{n=$path2;e={if ($_.SideIndicator -ne '<=') {'X'}}}
which would give a result like this:
Hash File C:\...\Fold1 C:\...\Fold2
---- ---- ------------ ------------
1DA53AC45042DB9413D1A6F055F7C5BA a.docx X X
2E9D15DF495521763D6CD5090B7DEF48 b.pdf X X
8DA3E12E4908F49055BD679D68848D5A c.pdf X
6F0D8230A93276D335CE656CEB54B764 d.doc X
Then export that output to a CSV via Export-Csv.

Related

How to select the file with the maximum number of the specified file

I want to keep only the file with the largest version of the specified zip file in the folder using powershell. I wrote a shell script but it returns all the files. How can I modify the script to select only the file with the largest version?
$files = Get-ChildItem -Filter "*.zip"
$max = $files |Measure-Object -Maximum| ForEach-Object {[int]($_.Split("_")[-1].Split(".")[0])}
$largestFiles = $files | Where-Object {[int]($_.Split("_")[-1].Split(".")[0]) -eq $max}
Write-Output $largestFiles
Expectation:
A1_Fantasic_World_20.zip
A1_Fantasic_World_21.zip
B1_Mythical_Realms_11.zip
B1_Mythical_Realms_12.zip
C1_Eternal_Frame_Corporation_2.zip
C1_Eternal_Frame_Corporation_3.zip
↓
A1_Fantasic_World_21.zip
B1_Mythical_Realms_12.zip
C1_Eternal_Frame_Corporation_3.zip
A1_Fantasic_World's biggest number is 21.B1_Mythical_Realms's is 12.C1_Eternal_Frame_Corporation's is 3. So I want to choose the biggest version of zip.
First you add the calculated properties to your file system objects you use for filtering. Then with a combination of Group-Object, Sort-Object and Select.Object you can filter the desired files.
$FileList =
Get-ChildItem -Filter *.zip |
Select-Object -Property *,
#{
Name = 'Title'
Expression = {($_.BaseName -split '_')[0..$(($_.BaseName -split '_').count - 2)] -join '_' }
},
#{
Name = 'Counter'
Expression = {[INT]($_.BaseName -split '_')[-1]}
}
$LastOnesList =
$FileList |
Group-Object -Property Title |
ForEach-Object {
$_.Group | Sort-Object -Property Counter | Select-Object -Last 1
}
$LastOnesList |
Select-Object -Property Name

PS - Get All SMB shares with permissions

I'm trying to get all smb shares on my windows server with all user permissions on them for inventory check.
This is what i have:
$Shares = Get-SmbShare
foreach($Share in $Shares)
{
Get-SmbShareAccess -Name $Share.Name | Where-Object {$_.AccountName -Match "DOMAINNAME"}
}
Which gets me all domain users with their shares and which access they have.
But it only shows name of folder. I would like its gonna show full path on the server (Not UNC)
And it would be exportable in csv format.
When i do:
$Shares = Get-SmbShare
foreach($Share in $Shares)
{
Get-SmbShareAccess -Name $Share.Name | Where-Object {$_.AccountName -Match "PRAGUELOFTS"} | Export-Csv -Path C:\perms.csv
}
It only exports the last user.
You can define your output columns very precisely when you pass to Select-Object an array of hashes in this format: #{name="xyz"; expr={ calculated value }}.
This way you can unify values from multiple sources, such as "share" and "share access", and manually calculated values, into one custom result.
Get-SmbShare | Where-Object Special -eq $false | ForEach-Object {
$share = $_
$share | Get-SmbShareAccess | Where-Object AccountName -Match "DOMAINNAME" | Select-Object #(
#{name="UncPath"; expr={ "\\" + $env:COMPUTERNAME + "\" + $share.Name }}
#{name="LocalPath"; expr={ $share.Path }}
#{name="Account"; expr={ $_.AccountName }}
#{name="Type"; expr={ $_.AccessControlType }}
#{name="Right"; expr={ $_.AccessRight }}
)
}
You can then go on and pipe this into Export-Csv -Path C:\perms.csv.
As for your second question - this
foreach ($Share in $Shares)
{
Get-SmbShareAccess -Name $Share.Name | Export-Csv -Path C:\perms.csv
}
only gives you the last result in the CSV file because it literally says "for each share, write a CSV file". You keep overwriting the same file in every loop iteration.
Collect all the results into a variable first
$results = foreach ($Share in $Shares) {
# ...
}
and then create the output file
$results | Export-Csv -Path C:\perms.csv

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}

How do I filter directories with powershell on the amount of files contained

I am having issues finding the correct syntax I need to filter my results on only listing directories with a file count of above a specified amount (600 in my case).
This is my code so far;
$server_dir= "D:\backup"
$export_dir= "C:\support\spcount.txt"
if($server_dir)
{
$folders = Get-ChildItem $server_dir
$output = #()
foreach($folder in $folders)
{
$fname = $folder.Name
$fpath = $folder.FullName
$fcount = Get-ChildItem $fpath | Measure-Object | Select-Object -Expand Count
$obj = New-Object psobject -Property #{FolderName = $fname; FileCount = $fcount} | Format-List;
$output += $obj
}
#Output
$output | Tee-Object -FilePath $export_dir | Format-list FileCount
}
And I am getting positive results with this, it is listing all Child Items within the backup dir however I need to filter this to only display and out too text format IF the directory contains 600 or more files.
Can anybody help me please?
I am fairly new too powershell so please pull me up if this code is not the greatest, I am forever wanting too learn.
Thanks!
I think I found the issue. It's that Format-List statement at the end of your object creation statement. It pipes the newly created object through Format-List, and thus transforms it into something else.
$obj = New-Object psobject -Property #{FolderName = $fname; FileCount = $fcount} | Format-List
So if you remove that last bit, you'll get the object you expect
$obj = New-Object psobject -Property #{FolderName = $fname; FileCount = $fcount}
So when you use the where statement to filter, you'll actually have a FileCount property to filter on.
I detected it by running the $output through Get-Member which showed me it wasn't the object with the expected properties.
So basically, here's your code, including fixes:
if($server_dir)
{
# *** Added the -directory flag, cause we don't need those pesky files ***
$folders = Get-ChildItem $server_dir -directory
$output = #()
foreach($folder in $folders)
{
$fname = $folder.Name
$fpath = $folder.FullName
$fcount = Get-ChildItem $fpath | Measure-Object | Select-Object -Expand Count
# *** Format-List was dropped here to avoid losing the objects ***
$obj = New-Object psobject -Property #{FolderName = $fname; FileCount = $fcount}
$output += $obj
}
# *** And now the filter and we're done ***
$output | where -Property FileCount -ge 600 | Tee-Object -FilePath $export_dir | Format-list FileCount
}
Note also the -directory to get only folders with get-childitem, and the -ge 600 (greater than or equal) instead of -gt 599 which is just a bit more obvious.
Remember that the Format-* statements actually transform the data passed through them. So you should only use those at the end of the pipeline to show data on screen or dump it to a file.
Don't use it to transform the data you still want to work with later on.
So in short you could do something like this to get that information.
Get-ChildItem C:\temp -Directory |
Select Name,#{Label="Count";Expression={(Get-Childitem $_ -file -Recurse).Count}} |
Where-Object{$_.Count -lt 10}
Let see if we can incorporate that in your code. Your if statement is also kind of pointless. Your variable contains a non-null \ non-zerolength string so it will always be True. You want it to work if the directory exists I imagine.
$server_dir= "D:\backup"
$export_dir= "C:\support\spcount.txt"
if(Test-Path $server_dir){
Get-ChildItem C:\temp -Directory |
Select Name,#{Label="Count";Expression={(Get-Childitem $_ -file -Recurse).Count}} |
Where-Object{$_.Count -lt 10} |
ConvertTo-Csv | Tee -File $export_dir | ConvertFrom-Csv
} Else {
Write-Warning "$server_dir does not exist."
}
Just working on getting this to file and screen with Tee just a moment.
I see 2 ways to do this.
Filter it in your output like this:
$output | where -property FileCount -gt 599 | # ... your code to write to the output
Or not store it in the output array if it doesn't match the condition:
if ($fcount -gt 599) {
$obj = New-Object psobject -Property #{FolderName = $fname; FileCount = $fcount} | Format-List;
$output += obj
}

How to sort the output in powershell

I am trying to list the content of a directory and try to sort them out the most recently created folders of file. I am using the following script but it is only giving me 1 item which is most recently created. All I would like to do is list all the folders in asc or desc order
$content = get-childitem 'C:\Users\tim\Desktop\tim_test'
write-output $content | Sort-Object LastWriteTime -Descending | Select-Object -First 1
Your "Select-Object -First 1" gives you only the first file, to get all files, remove that. Like so:
$newPath = 'C:\Users\tim\Desktop\tim_test2'
$content = get-childitem 'C:\Users\tim\Desktop\tim_test' | Sort-Object LastWriteTime -Descending
Write-Host $content
# Part 2
$oldFile = $content | Select-Object -first 1
$prefix = Read-Host "Enter prefix"
$newFile = "$newPath\$prefix$oldFile"
Copy $file $newFile
Something like that should work :)

Resources