Using PowerShell, identify the last TWO most recently created files in a single directory, compare them, and spit out only the differences to a file - windows

I need some assistance on my attempt at my 3rd PowerShell script. I have a report being generated daily. It puts the information I need into a txt file in a Reports folder on my desktop. I'm needing a way to compare the most recent LAST report with the newest and then output the differences to a different file for review at a later date. This script will run as a .bat on every computer getting these reports (obviously only running if the computer is turned on so the dates will not always be back to back).
There will only be a single write to this directory every 24 hours which will be the daily generated report. The daily report file is named with the computer name in file name aa111111-report-currentdate.txt. If the 'Report' directory contains 2 or more files, run a compare on the most recently created file and the previously created file then spit out the differences to a new file in a different directory to identify new changes manually. Here is what I have:
if ((Get-ChildItem C:\users\x\Desktop\Report -File | Measure-Object).count -ge 2) {
Compare-Object (Get-ChildItem 'C:\users\x\Desktop\Report' | sort LastWriteTime | select -Last 1) -DifferenceObject (Get-ChildItem 'C:\users\x\Desktop\Report' | sort LastWriteTime | select -Last 2) -PassThru | Where-Object {$_.Name -match '^[a-zA-Z]{2}\d{6}[-]report[-]\d{4}[-]\d{2}[-]\d{2}\.txt$' -and $_.SideIndicator -eq "=>"} | Out-File -FilePath 'C:\users\x\Desktop\Report_Differences\differences.txt' -Width 9999999
}
(Get-ChildItem 'C:\users\x\Desktop\Report' | sort LastWriteTime | select -Last 1) looks like this:
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 5/23/2020 8:45 PM 670146 DESKTOP-aa111111-report-2020-05-23.txt
(Get-ChildItem 'C:\users\x\Desktop\Report' | sort LastWriteTime | select -Last 2) looks like this:
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 5/23/2020 8:45 PM 670146 DESKTOP-aa111111-report-2020-05-23.txt
-a---- 5/24/2020 9:45 PM 793676 DESKTOP-aa222222-report-2020-05-24.txt
Problem 1:
My current solution seems to identify both files as a single query and I'm not sure how to have PowerShell understand I need them identified separately to do a diff. How can I specify a compare on only the 2 most recently created files in the directory, diff them, then spit out results to another file in a separate directory WITHOUT knowing the exact file name (only the regex pattern which will match).
Extra: Is there a method to get the difference file to be formatted such as 'date of last file vs date of today' so I know which files/dates found the differences?
UPDATE
$last2createdfiles = Get-ChildItem 'C:\users\x\Desktop\Report' | sort LastWriteTime | select -Last 2
However, the below PowerShell does not output the file differences to a file. It only shows the output of the file name properties of $last2createdfiles[0] such as mode, lastwritetime,length, and name.
if ((Get-ChildItem C:\users\x\Desktop\Report -File | Measure-Object).count -ge 2) {
Compare-Object -ReferenceObject $last2createdfiles[0] -DifferenceObject $last2createdfiles[1] -PassThru | Where-Object {$_.SideIndicator -eq "=>"} | Out-File -FilePath 'C:\users\x\Desktop\Report_Differences\differences.txt'
}
What am I missing?

Related

I'm a novice at Powershell and im trying to complete a task

I'm starting to work with Powershell and I've been doing some courses online. At this moment I'm stuck in a Challenge, I hope some good soul can help me with the instructions from the course.
Instructions are:
1-Find out what Windows features are installed on the server machine. (I'm remoting command to a computer named "Server")
2-Select only the data stored in the Name and InstallState columns.
3-Sort the data by the Name property in ascending (alphabetical) order.
4-Make sure the output format is set to table
5-Save the final output into a file called C:\features.txt on your desktop machine.
What I have come up with is this:
Invoke-Command -ComputerName Server -ScriptBlock{
>> Get-WindowsFeature | Select-Object -Property Name, InstallState | Sort-Object -Property Name | Format-Table
>> } | Out-File C:\features.txt
I have tried both with and without the select-object command since I know the format-table command works almost the same in this case. Thank u!
When ever I invoke a scriptblock on a remote computer i assign it to a variable and then handle the results this:
$myResults= Invoke-Command -ComputerName Server -ScriptBlock{…}
Foreach ($item in $myResults){
Write-host” $item.name /
$item.nstallState”
}
You use the pipeline rather excessively which is not wrong but for me personally still a beginner my self it is not easy to totally comprehend what each of them is exactly returning and forwarding to the next. Dont try to write a perfect line with several pips in the first attempt. Start with the smallest fastest fraction of the task. In this case yust get them all out without filter and pipelines. Then work your way from there, make little changes, use write-host to display the results and trail and error yourself to a deeper understanding of each of them. And then in the end u can chain them up.
As per my comment for example.
# 1-Find out what Windows features are installed on the server machine.
Get-WindowsFeature
# Results
<#
#>
# 2-Select only the data stored in the Name and InstallState columns.
Get-WindowsFeature |
Select-Object -Property Name, InstallState
# Results
<#
#>
# 3 - Sort the data by the Name property in ascending (alphabetical) order.
Get-WindowsFeature |
Select-Object -Property Name, InstallState |
Sort-Object -Property Name
# Results
<#
#>
# 4-Make sure the output format is set to table
Get-WindowsFeature |
Select-Object -Property Name, InstallState |
Sort-Object -Property Name |
Format-Table # though this is not needed, since table is the default for less than 5 properties.
# Results
<#
#>
<#
# 5-Save the final output into a file called C:\features.txt on your desktop
machine.
#>
Get-WindowsFeature |
Select-Object -Property Name, InstallState |
Sort-Object -Property Name |
Export-Csv -Path 'SomePathName' -NoTypeInformation -Append
# (I'm remoting command to a computer named "Server")
$Computers |
ForEach-Object {
Invoke-Command -ComputerName $PSItem.ComputerName -ScriptBlock{
Get-WindowsFeature |
Select-Object -Property Name, InstallState |
Sort-Object -Property Name |
Export-Csv -Path 'SomePathName' -NoTypeInformation -Append
}
}

Powershell script to list all open Explorer windows

This question shows a Powershell script to generate a list of open File Explorer windows and their path.
My goal is to capture the currently open set of explorer windows, and write out a CMD file with commands like: C:\WINDOWS\explorer.exe /e, "C:\open\this\folder"
So I would like to have the full path and folder name in normal path notation. This is what is showing in titlebar of the Explorer Windows: "C:\open\this\Favorite folder"
The proposed code is:
function Get-WindowTitle($handle) {
Get-Process |
Where-Object { $_.MainWindowHandle -eq $handle } |
Select-Object -Expand MainWindowTitle
}
$app = New-Object -COM 'Shell.Application'
$app.Windows() |
Select-Object LocationURL, #{n='Title';e={Get-WindowTitle $_.HWND}}
As shown above, LocationURL provides a full path in an escaped-URL style:
file:///C:/open/this/Favorite%20%folder"
The #{n='Title';e={Get-WindowTitle $_.HWND}} component produces a column "Title" which is truncated to 5 characters:
C:\...
The full output for one explorer window looks like:
LocationURL Title
----------- -----
file:///C:/open/this/Favorite%20%folder C:...
I found I could avoid the truncation by padding the string 'Title' with many spaces. That string's width seems to determine the maximum width of the output.
Still, I observe that only about 60% of the open explorer windows list a path. The rest are just a blank line.
I tried "$app.Windows() | Select-Object LocationName", but the output only contains the Explorer folder name only, not the full path and folder that is displayed in the Explorer title.
Another mystery is why the script runs so slowly. If I have 10 explorer windows open, the script runs for 30 seconds, taking about 3 seconds per path.
For this script:
function Get-WindowTitle($handle) {
Get-Process |
Where-Object { $_.MainWindowHandle -eq $handle } |
Select-Object -Expand MainWindowTitle
}
$app = New-Object -COM 'Shell.Application'
$app.Windows() |
Select-Object LocationName,#{n=' ------------Title---------------- ';e={Get-WindowTitle $_.HWND}}
This is the output (with some redacting with *** for privacy)
PS C:\E***> .\OpenExplorer.ps1
LocationName ------------Title----------------
------------ ----------------------------------------------------------------------------------
2019-07
Ame****
2019 Priv...
2019-10-3... C:\E\Event Presentations\2019-10-31 Priv**********bcast
E C:\E
5G Brief ... C:\E\Tech************ing\5G Brief (2018)
36 Series...
2019 DE* ... C:\E\16*****N\2019 DE*******************
Newsletters C:\E\Newsletters
Reports C:\E\Tech************ing\Reports
2019-10-2... C:\E\16**********s\2019-10-29 *********************
2019-11 C:\Data\Docs\Stand*********24\2019-11
UB****
Financial... C:\E\Financ************
Expenses C:\E\Internal\Expenses
E C:\E
E***
I assume what you're really interested is the local filesystem paths of the open Explorer windows, not necessarily the window titles (which aren't guaranteed to reflect the full paths).
Somewhat obscurely, the window objects returned by the .Windows() method contain the local path representation in their .Document.Folder.Self.Path property.
(New-Object -ComObject 'Shell.Application').Windows() | ForEach-Object {
$localPath = $_.Document.Folder.Self.Path
"C:\WINDOWS\explorer.exe /e, `"$localPath`""
}
The above produces output such as:
C:\WINDOWS\explorer.exe /e, "C:\Users\jdoe"
C:\WINDOWS\explorer.exe /e, "C:\Program Files"
You can output this to a batch file file as needed, e.g. by appending | Set-Content file.cmd to the above command.
Note: The windows are listed in the order in which they were created, so you cannot infer which among them was most recently activated. See this answer for a solution that finds the topmost File Explorer window and determines the path shown in it.
I found I could avoid the truncation
The truncation is just a display artifact - the data is still there.
You can make the data visible in one of two ways:
pipe to Format-Table -AutoSize to make sure that column values aren't truncated, space permitting
pipe to Format-List, which will show each property on its own line (line-wrapping overly long values).

Recursively count filtered files in subfolders and group results

I want to count all files with a particular prefix in a directory and then display the results based on each sub directory.
The directory tree is as follows
Test
January
sms20180101_110.unl
rec20180101_110.unl
data20180101_110.unl
February
sms20180201_110.unl
rec20180201_110.unl
data20180201_110.unl
March
sms20180301_110.unl
rec20180301_110.unl
data20180301_110.unl
So, I need to count for example the total data files in each subdirectory and display results as follows
January 1
February 5
March 10
I wrote the following command in Powershell
Get-ChildItem -Path D:\Test -Filter *data* -Force -Recurse | Measure-Object | %{$_.Count}
So, the problem is it is giving me the total files in the root directory
A similar question was asked here Recursively count files in subfolders but I have not been able to customize the solutions provided here to my need
Based on your scenario, you can use Group-Object like this -
Get-ChildItem -Path D:\Test -Filter *data* -Force -Recurse | Group-Object -Property Directory | Select-Object Name, Count
This will list all the name of the folders and sub-folders along with the count of files having data in it's name.

Checking for a file whether it is readable and regular in powershell

I'm new to powershell and I want to check if file in readable and regular. In unix we can do it in one line by using -f & -r. For example the following shell script function accepts filename as argument and checks the readability and regularity of file, whats the powershell equivalent for this?
_ChkRegularFile_R() # checks whether a file is regular and readable
{
_CRFRfilename=$1 # name of the file to be checked
_CRFRsts=1 # default is error
if [ -f "$_CRFRfilename" ]
then
if [ -r "$_CRFRfilename" ]
then
_CRFRsts=0 # success: regular file is readable
fi
fi
return $_CRFRsts
}
To test if a file is readable, you try to open it. If you get an error, then it's not readable. You need to either trap or catch exceptions or stop on errors, as appropriate. Remember, Windows locks files that are open for writing, so applications need to expect that they sometimes can't open a file.
If you absolutely have to, you can use something like this to test if you can read a file:
try {
[System.IO.File]::OpenRead($FullPathName).Close()
$Readable = $true
}
catch {
$Readable = $false
}
And this to test if you can write to a file:
try {
[System.IO.File]::OpenWrite($FullPathName).Close()
$Writable = $true
}
catch {
$Writable = $false
}
That logic is fairly easy to wrap into a function if you really need it.
As far as file types, nearly everything in the file system in Windows is a plain file or a directory, since Windows doesn't have the "everything is a file" convention. So, normally you can test as follows:
# Test if file-like
Test-Path -Path $Path -Leaf
# Test if directory-like
Test-Path -Path $Path -Container
If you're working with a FileInfo or DirectoryInfo object (i.e., the output of Get-Item, Get-ChildItem, or a similar object representing a file or directory) you'll have the PSIsContainer property which will tell you if the item is a file or a directory.
That covers probably 99.999% of cases.
However, if you need to know if something is an NTFS hard link to a file (rare, but oldest), an NTFS junction to a directory, an NTFS symlink, an NTFS volume mount point, or any type of NTFS reparse point, it gets much more complicated. [This answer does a good job describing the first three.]
Let's create a simple NTFS folder to test with:
# Create a test directory and change to it.
New-Item -Path C:\linktest -ItemType Directory | Select-Object -ExpandProperty FullName | Push-Location
# Create an empty file
New-Item -Path .\file1 -ItemType file -Value $null | Out-Null
New-Item -Path .\file2 -ItemType file -Value $null | Out-Null
# Create a directory
New-Item -Path .\dir1 -ItemType Directory | Out-Null
# Create a symlink to the file
New-Item -ItemType SymbolicLink -Path .\sfile1 -Value .\file1 | Out-Null
# Create a symlink to the folder
New-Item -ItemType SymbolicLink -Path .\sdir1 -Value .\dir1 | Out-Null
# Create a hard link to the file
New-Item -ItemType HardLink -Path .\hfile1 -Value .\file1 | Out-Null
# Create a junction to the folder
New-Item -ItemType Junction -Path .\jdir1 -Value .\dir1 | Out-Null
# View the item properties
Get-ChildItem -Path . | Sort-Object Name | Format-Table -Property Name, PSIsContainer, LinkType, Target, Attributes -AutoSize
Your output will be:
Name PSIsContainer LinkType Target Attributes
---- ------------- -------- ------ ----------
dir1 True {} Directory
file1 False HardLink {C:\linktest\hfile1} Archive
file2 False {} Archive
hfile1 False HardLink {C:\linktest\file1} Archive
jdir1 True Junction {C:\linktest\dir1} Directory, ReparsePoint
sdir1 True SymbolicLink {C:\linktest\dir1} Directory, ReparsePoint
sfile1 False SymbolicLink {C:\linktest\file1} Archive, ReparsePoint
Note that both file1 and hfile1 are hard links, even though file1 wasn't created as such.
To clean up the above garbage, do:
Get-ChildItem -Path C:\linktest\ | ForEach-Object { $_.Delete() }
There's a bug in Remove-Item with deleting some container links which prevents the command from removing the items.
The general solution would be to get the item and test it:
# Get the item. Don't use Get-ChildItem because that will get a directory's contents
$Item = Get-Item -Path $Path
# Is it a container
$Item.PSIsContainer
# Is it a link of some kind?
[System.String]::IsNullOrWhiteSpace($Item.LinkType)
$Item.LinkType -eq 'Junction'
# Is it a Reparse Point?
($Item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -eq [System.IO.FileAttributes]::ReparsePoint
There are several other potential attributes, too:
PS> [System.Enum]::GetNames([System.IO.FileAttributes])
ReadOnly
Hidden
System
Directory
Archive
Device
Normal
Temporary
SparseFile
ReparsePoint
Compressed
Offline
NotContentIndexed
Encrypted
IntegrityStream
NoScrubData
Note that Device is documented as reserved for future use. Ain't no device file type in Windows.
For volume mount points, I'm not 100% sure how those look. I know you can create them on Windows 8.1 and later with Get-Partition followed by an appropriate Add-PartitionAccessPath, but I'm on Windows 7 currently. I'm afraid I have no means of testing this at the moment.
Finally, I have no idea how exactly PowerShell Core 6.0 on Linux handles file types.
Soooo,,,,
This is not something I regulary do, but if memory serves. In *nix, a regular file contains data, is a direcotry,
Again, not somehting I do/have to worry about under normal PoSH stuff.
So you are testing for where the object is a writable file (and / or non-zero) or a directory or binary?
So, in PoSH, prior to v3... you do something like this...
$IsDir = {$_.PsIsContainer}
$IsFile = {!$_.PsIsContainer}
ls D:\Temp | Where $IsDir
lsectory: D:\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 1/4/2018 2:31 PM ArchiveDestination
d----- 1/4/2018 1:40 PM ArchiveSource
d----- 1/1/2018 3:34 PM diff
...
ls D:\Temp | Where $IsFile
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 6/7/2017 5:28 PM 512 CombinedSources07Jun2017.txt
-a---- 2/24/2018 6:29 PM 115 EmpData.csv
-a---- 11/18/2017 6:47 PM 11686 fsoVolume.docx
...
PoSH V3 and higher. This is supported natively e.g.:
ls -directory
ls -ad
ls -file
ls -af
Of course any of the above can be set to just return true or false using if/then or try/catch.
If all the above is a bit more typing than you'd like then you can create your own function and give it whatever alias you choose, well, as long as it's not an alias already in use.
See the help files ...
# Get parameters, examples, full and Online help for a cmdlet or function
(Get-Command -Name Get-ChildItem).Parameters
Get-help -Name Get-ChildItem -Examples
Get-help -Name Get-ChildItem -Full
Get-help -Name Get-ChildItem -Online
Get-Help about_*
Get-Help about_Functions
Get-Alias -Definition Get-ChildItem
# Find all cmdlets / functions with a target parameter
Get-Help * -Parameter Append
# All Help topics locations
explorer "$pshome\$($Host.CurrentCulture.Name)"
Of course you can check / modify file attributes as well. See this article on the topic:
File Attributes in PowerShell
Fun with file and folder attributes, via PowerShell and the DIR command.
https://mcpmag.com/articles/2012/03/20/powershell-dir-command-tricks.aspx
So, you could do something like this, to achieve the same attribute check
Get-ChildItem -Path $FilePath -File -Force | Where {$_.Attributes -notmatch 'ReadOnly'}
Or a function wiht an alias.
Function Test-RegularFile
{
[CmdletBinding()]
[Alias('trf')]
Param
(
[string]$FilePath
)
try
{
Get-ChildItem -Path $FilePath -File -Force `
| Where {$_.Attributes -notmatch 'ReadOnly'}
"$FilePath is a regular file" # success: regular file is readable
}
catch
{
Write-Warning -Message "$FilePath is not a Regular file."
}
}
trf -FilePath D:\Temp\fsoVolume.txt
Since you are new to PoSH, it reall important / vital that you get a base understanding before looking at conversion comparisons.
See this post for folks providing some paths for learning PowerShell.
https://www.reddit.com/r/PowerShell/comments/7oir35/help_with_teaching_others_powershell
To test whether it's a regular file:
Test-Path -PathType Leaf foo.txt
To test whether it's readable:
Get-ChildItem foo.txt | ? { $_.Mode -match 'r'}
To test whether it's hidden:
Get-ChildItem -force foo.txt | ? { $_.Mode -match 'h'}

Why folder size is different from result of PowerShell?

I am running PowerShell code to check a folder size. I just noticed that the result is different from what I see in Windows GUI of folder properties.
part of PowerShell code:
#{label="Size" ; expression={(Get-childitem "c:\windows" -recurse | measure-object length -sum).sum}}
Results are not the same....?
BTW, how to display the size in GB based on the code above?
Hint please!
The reason you get different results is hidden files and folders. To account for these use -Force switch on Get-childitem:
#{label="Size" ; expression={(Get-childitem -force "c:\windows" -recurse | measure-object length -sum).sum}}
To get your total into GBs just divide the result by 1Gb, ie:
#{label="Size" ; expression={(Get-childitem -force "c:\windows" -recurse | measure-object length -sum).sum/1Gb}}
Or to round it up to one decimal point:
#{label="Size" ; expression={[math]::round(((Get-childitem -force "c:\windows" -recurse | measure-object length -sum).sum/1Gb),1)}}

Resources