Compare-Object returning unexpected output - powershell

I have created a small function that captures Get-Volume into a local file. The next time the function runs, it compares the output of a fresh Get-Volume with the one previously saved to the file system.
This function works perfectly for services but is strangely returning a volume as 'different' even though we can see from the output it is not.
function Compare-Volumes{
$Path = "$Env:PROGRAMDATA\VACS\states\"
$File = "volumes.csv"
$Volumes = Get-Volume | Select-Object OperationalStatus, HealthStatus, DriveType, FileSystemType, DedupMode, UniqueId, AllocationUnitSize, DriveLetter, FileSystem, FileSystemLabel, Size
if (![System.IO.File]::Exists($Path+$File)){
$Volumes | Export-CSV -Path $Path$File -Force
}else{
# Load file to object, get differences, submit to API, replace previous snapshot in file with new one
$Snapshot = Import-CSV -Path "$Path$File"
$StatusChanges = Compare-Object -ReferenceObject ($Snapshot) -DifferenceObject ($Volumes) -Property OperationalStatus, HealthStatus, DriveType, FileSystemType, DedupMode, UniqueId, AllocationUnitSize, DriveLetter, FileSystem, FileSystemLabel, Size -IncludeEqual
$StatusChanges
$Volumes | Export-CSV -Path $Path$File -Force
}
}
My expected results are that everything returns as equal/unchanged (==) as none of the properties change, as is clear in the output below. Yet for some reason, the SideIndicator property added by Compare-Object is indicating value differences for the volume labelled Recovery.
OperationalStatus : Unknown
HealthStatus : Healthy
DriveType : CD-ROM
FileSystemType : Unknown
DedupMode : Disabled
UniqueId : \\?\Volume{2b4803c9-1ebe-11e6-9bed-005056c00008}\
AllocationUnitSize : 0
DriveLetter : E
FileSystem :
FileSystemLabel :
Size : 0
SideIndicator : ==
OperationalStatus : OK
HealthStatus : Healthy
DriveType : Fixed
FileSystemType : NTFS
DedupMode : NotAvailable
UniqueId : \\?\Volume{f688d14f-0ee7-11e5-b210-806e6f6e6963}\
AllocationUnitSize : 4096
DriveLetter : C
FileSystem : NTFS
FileSystemLabel : Windows
Size : 953903214592
SideIndicator : ==
OperationalStatus : Unknown
HealthStatus : Healthy
DriveType : CD-ROM
FileSystemType : Unknown
DedupMode : Disabled
UniqueId : \\?\Volume{f688d152-0ee7-11e5-b210-806e6f6e6963}\
AllocationUnitSize : 0
DriveLetter : D
FileSystem :
FileSystemLabel :
Size : 0
SideIndicator : ==
OperationalStatus : OK
HealthStatus : Healthy
DriveType : Fixed
FileSystemType : NTFS
DedupMode : NotAvailable
UniqueId : \\?\Volume{f688d14e-0ee7-11e5-b210-806e6f6e6963}\
AllocationUnitSize : 4096
DriveLetter :
FileSystem : NTFS
FileSystemLabel : Recovery
Size : 6291451904
SideIndicator : =>
OperationalStatus : OK
HealthStatus : Healthy
DriveType : Fixed
FileSystemType : NTFS
DedupMode : NotAvailable
UniqueId : \\?\Volume{f688d14e-0ee7-11e5-b210-806e6f6e6963}\
AllocationUnitSize : 4096
DriveLetter :
FileSystem : NTFS
FileSystemLabel : Recovery
Size : 6291451904
SideIndicator : <=

Strangely it is the DriveLetter property which compares falsely
with volumes which doesn't have one (like the recovery partition).
Presumably you'll have to include a Select-Object with a calculated property
which also checks the DriveLetter [string]::IsNullOrEmpty()
to avoid comparing $Null with the stringified output "" of Export-Csv
Your script, a bit streamlined:
## Q:\Test\2018\12\31\SO_53990220.ps1
function Compare-Volumes{
$FilePath = Join-Path "$Env:PROGRAMDATA\VACS\states\" "volumes.csv"
$Volumes = Get-Volume | Select-Object OperationalStatus,HealthStatus,DriveType,
FileSystemType, DedupMode,UniqueId,AllocationUnitSize,FileSystemLabel,FileSystem,Size,
#{n='DriveLetter';e={if([string]::IsNullOrEmpty($_.DriveLetter)){""}else{$_.DriveLetter}}}
if (Test-Path $FilePath){
# Load file to object, get differences, submit to API, replace previous snapshot in file with new one
$Snapshot = Import-CSV -Path $FilePath
$StatusChanges = Compare-Object -ReferenceObject ($Snapshot) -DifferenceObject ($Volumes) `
-IncludeEqual -Property OperationalStatus,HealthStatus,DriveType,FileSystemType,
DedupMode,UniqueId,AllocationUnitSize,FileSystemLabel,FileSystem,Size,
DriveLetter
$StatusChanges
}
$Volumes | Export-CSV -Path $FilePath -Force -NoTypeInformation
}
Compare-Volumes

Related

How can I use a variable as Application Pool name?

The following code gives me the default app pool, converted to string. When I pass this variable
$appPoolName = Get-IISAppPool | Select-Object Name | Where-Object {$_.Name -like "Default" } | Out-String
(Get-IISAppPool $appPoolName).processmodel.username
I get the following error
Why not just do this:
$appPoolName = (Get-IISAppPool | Where-Object Name -Match 'Default').Name
(Get-IISAppPool $appPoolName).processmodel
# Results
<#
IdentityType : ApplicationPoolIdentity
IdleTimeout : 00:20:00
IdleTimeoutAction : Terminate
LoadUserProfile : True
MaxProcesses : 1
PingingEnabled : True
PingInterval : 00:00:30
PingResponseTime : 00:01:30
Password :
ShutdownTimeLimit : 00:01:30
StartupTimeLimit : 00:01:30
UserName :
...
#>

Get Drive letter from Volume ID

I want to use drive letter instead of Volume ID like below. How can I do that ?
Thanks,
My output :
VolumeName OriginatingMachine InstallDate
\\?\Volume{3c0a6eed-1b4c-4e90-a25b-8af1af46e368}\ app01.contoso.com 4/13/2021 6:03:34 PM
\\?\Volume{de5c18ac-56e1-4efa-afb4-abaf476a99a9}\ app01.contoso.com 4/13/2021 6:03:34 PM
My desired output :
VolumeName OriginatingMachine InstallDate
E: app01.contoso.com 4/13/2021 6:03:34 PM
X: app01.contoso.com 4/13/2021 6:03:34 PM
Command:
Get-CimInstance Win32_ShadowCopy | Where InstallDate -lt ([datetime]::Now.AddDays(-5)) | Select-Object VolumeName,OriginatingMachine,InstallDate
Get-CimInstance Win32_ShadowCopy output :
Caption :
Description :
InstallDate : 9/17/2021 9:22:06 PM
Name :
Status :
ClientAccessible : False
Count : 11
DeviceObject : \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1234
Differential : True
ExposedLocally : False
ExposedName :
ExposedPath :
ExposedRemotely : False
HardwareAssisted : False
ID : {3C8DAB36-F083-4C68-84BA-E339B8A18AF3}
Imported : False
NoAutoRelease : True
NotSurfaced : False
NoWriters : False
OriginatingMachine : app01.contoso.com
Persistent : True
Plex : False
ProviderID : {B5946137-7B9F-4925-AF80-51ABD60B20D5}
ServiceMachine : app01.contoso.com
SetID : {D3D15CE2-9FFC-4A22-B03C-77D7FCB7D40E}
State : 12
Transportable : False
VolumeName : \\?\Volume{85e62cfa-2e9c-4a46-af5a-f0748acd60b6}\
PSComputerName :
The Win32_ShadowCopy command returns Win32_ShadowCopy objects, which have a number of fields all described here.
Some of the fields provided in ShadowCopy happen to match other WMI Classes which have info about Drive Letters, namely the VolumeName field. This field happens to also be present on both the ShadowCopy and Win32_Volume class, the latter of which has the drive letter property we need!
All we have to do is lookup all of the shadow copies, then lookup all of the volumes on the machine, then we can loop through the copies to find the matching volumes. If you have some really complex mounting logic, you'll need to add some extra logic though in the foreach section.
So we would make a quick little function to do this lookup for us, which would look like this:
function Get-Win32ShadowVolumeDiskInfo{
$shadowCopies = Get-CimInstance Win32_ShadowCopy
$volumes = Get-CimInstance Win32_Volume
$returnObject = #()
foreach ($copy in $shadowCopies){
$matchingVolume = $volumes | Where DeviceID -eq $copy.VolumeName
$returnObject += [PSCustomObject]#{
VolumeName=$matchingVolume.Name;
OriginatingMachine = $copy.OriginatingMachine;
InstallDate = $copy.InstallDate}
}
$returnObject
}

How to find the title for a PDF from the metadata?

How can I get the title for a PDF file after having renamed the file itself?
PSPath : Microsoft.PowerShell.Core\FileSystem::/home/nicholas/to/99.pdf
PSParentPath : Microsoft.PowerShell.Core\FileSystem::/home/nicholas/to
PSChildName : 99.pdf
PSDrive : /
PSProvider : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
Mode : -----
ModeWithoutHardLink : -----
VersionInfo : File: /home/nicholas/to/99.pdf
InternalName:
OriginalFilename:
FileVersion:
FileDescription:
Product:
ProductVersion:
Debug: False
Patched: False
PreRelease: False
PrivateBuild: False
SpecialBuild: False
Language:
BaseName : 99
Target :
LinkType :
Length : 592483
DirectoryName : /home/nicholas/to
Directory : /home/nicholas/to
IsReadOnly : False
FullName : /home/nicholas/to/99.pdf
Extension : .pdf
Name : 99.pdf
Exists : True
CreationTime : 2/19/2021 11:45:18 PM
CreationTimeUtc : 2/20/2021 7:45:18 AM
LastAccessTime : 2/20/2021 2:02:36 AM
LastAccessTimeUtc : 2/20/2021 10:02:36 AM
LastWriteTime : 2/19/2021 11:45:18 PM
LastWriteTimeUtc : 2/20/2021 7:45:18 AM
Attributes : Normal
PS /home/nicholas/to>
PS /home/nicholas/to> Get-ChildItem -Path ./ –File | Select-Object -Property *
This is to bulk import PDF files into calibre, which, notably, seems to recognize duplicates and even displays some titles. Is it parsing the PDF file itself, or gleaning this from meta-data?
For this, you can use pdfinfo.exe which you can find as part of the free Xpdf command line tools.
After you have downloaded and extracted the zip file, copy pdfinfo.exe to some directory and make sure you unblock it, either by right-click or by using PowerShell
Unblock-File -Path 'Where\Ever\You\Have\Copied\It\To\pdfinfo.exe'
Using that, to get the original title as stored in the pdf, you do
$title = ((& 'D:\Test\pdfinfo.exe' 'D:\Test\test.pdf' |
Where-Object { $_ -match '^Title:' }) -split ':', 2)[-1].Trim()

Receiving invalid data via WMI

I've had a script running for the better half of a year now that checks for disk space, should it be low it sends out an email been working just fine.
Today I received an alert saying that that Disk space was at 0gb.. I logged into the server and had about 30gbs available.
Part of code that checks the space.
[decimal]$thresholdspace = 5
$tableFragment= Get-WMIObject -ComputerName $computer Win32_LogicalDisk `
| select __SERVER, DriveType, VolumeName, Name, #{n='Size (Gb)' ;e={"{0:n2}" -f ($_.size/1gb)}},#{n='FreeSpace (Gb)';e={"{0:n2}" -f ($_.freespace/1gb)}}, #{n='PercentFree';e={"{0:n2}" -f ($_.freespace/$_.size*100)}} `
| Where-Object {$_.DriveType -eq 3 -and [decimal]$_.PercentFree -lt [decimal]$thresholdspace}
So I opened up an ISE session and did the following.
$computer = 'Server_01'
Get-WMIObject -ComputerName $computer Win32_LogicalDisk | select __SERVER, DriveType, VolumeName, Name,FreeSpace
and it responded with:
__SERVER : Server_01
DriveType : 2
VolumeName :
Name : A:
FreeSpace :
__SERVER : Server_01
DriveType : 3
VolumeName :
Name : C:
FreeSpace : 31372767232
__SERVER : Server_01
DriveType : 5
VolumeName :
Name : E:
FreeSpace :
So I then ran the part of my code that is getting space and it returned with the following:
__SERVER : Server_01
DriveType : 3
VolumeName :
Name : C:
Size (Gb) : 0.00
FreeSpace (Gb) : 0.00
PercentFree :
So I ran the simple WMI query and it responded with no data..
__SERVER : Server_01
DriveType : 2
VolumeName :
Name : A:
FreeSpace :
__SERVER : Server_01
DriveType : 3
VolumeName :
Name : C:
FreeSpace :
__SERVER : Server_01
DriveType : 5
VolumeName :
Name : E:
FreeSpace :
Any idea as to why I'm getting invalid or what appears to be incomplete data? I've never seen anything like this, and have not ran into this issue at all before. Nothing has changed on either server it is running from.

getting the values of specific string values in registry keys with powershell

Using the following powershell script:
Get-ChildItem hkcu:\Test\Universe\Datawink\Switch1\Devices | ForEach-Object {Get-ItemProperty $_.pspath}
I am able to get the following output:
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Test\Universe\Datawink\Switch1\Devices\Entry1
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Test\Universe\Datawink\Switch1\Devices
PSChildName : Entry1
PSProvider : Microsoft.PowerShell.Core\Registry
Device : 2882881001
Recordable : Yes
Active : Yes
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Test\Universe\Datawink\Switch1\Devices\Entry2
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Test\Universe\Datawink\Switch1\Devices
PSChildName : Entry2
PSProvider : Microsoft.PowerShell.Core\Registry
Device : 2882881002
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Test\Universe\Datawink\Switch1\Devices\Entry3
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Test\Universe\Datawink\Switch1\Devices
PSChildName : Entry3
PSProvider : Microsoft.PowerShell.Core\Registry
Device : 2882881003
Which is great, it's showing me the infomation I want, but all I really need is the values of the string entry called 'Device' so all I actually want is the output of the script to look like this:
Device : 2882881001
Device : 2882881002
Device : 2882881003
or ideally, this:
2882881001
2882881002
2882881003
What's the simplest way to achieve this?
This should work as well:
$path = 'hkcu:\Test\Universe\Datawink\Switch1\Devices'
Get-ChildItem $path | Get-ItemProperty | Select-Object -ExpandProperty Device
Get-ChildItem hkcu:\Test\Universe\Datawink\Switch1\Devices | ForEach-Object {Get-ItemProperty $_.pspath} | where-object {$_.Device} | Foreach-Object {$_.Device}
will give you the requested output.
(In your example the Where-Object is not really required)