PowerShell Find Driver Version - parsing

Using DevCon utility, I ran this command to get the list of all of the drivers installed in the computer.
devcon.exe driverfiles * > drivers.txt
The output looks like this:
USB\ROOT_HUB20\4&361B340A&0
Name: USB Root Hub
Driver installed from C:\windows\INF\usbport.inf [ROOTHUB.Dev]. 2 file(s) used by driver:
C:\windows\system32\drivers\usbhub.sys
C:\windows\system32\drivers\usbd.sys
ACPI\PNP0C09\1
USB\ROOT_HUB20\4&361B340A&1
Name: USB Root Hub
Driver installed from C:\windows\INF\usbport.inf [ROOTHUB.Dev]. 2 file(s) used by driver:
C:\windows\system32\drivers\usbhub.sys
C:\windows\system32\drivers\usbd.sys
I then used PowerShell script to parse the file. Thanks to TheMadTechnician.
(Get-Content C:\Path\To\File.txt) -join "`r`n" -Split "(?m)^(?=\S)" |
Where{$_} |
ForEach{
Clear-Variable Files,Driver,Name,HardwareID
$Files = #()
$HardwareID = ($_ -split "`r`n")[0].trim()
Switch -regex ($_ -split "`r`n"){
"^\s+Name:" {$Name = ($_ -split ':',2)[-1].trim();Continue}
"^\s+.:\\" {$Files += $_.trim();continue}
"^\s+Driver" {$Driver = [RegEx]::Matches($_,"(?<=Driver installed from )(.+?)(?= \[)").value;continue}
}
[PSCustomObject]#{'HardwareID' = $HardwareID;'Name' = $Name; 'Files' = $Driver; 'FileVersion' = If(Test-Path $_.Trim()){[System.Diagnostics.FileVersionInfo]::GetVersionInfo($_.trim()).FileVersion}}
$Files | ForEach{ [PSCustomObject]#{'HardwareID' = $HardwareID;'Name' = $Name; 'Files' = $_; 'FileVersion' = If(Test-Path $_.Trim()){[System.Diagnostics.FileVersionInfo]::GetVersionInfo($_.trim()).FileVersion}Else{'File Not Found'}}}
Now, the output after running the script is:
HardwareID Name Files FileVersion
---------- ---- ----- -----------
USB\ROOT_HUB20\4&361B340A&0 USB Root Hub C:\windows\INF\usbport.inf
USB\ROOT_HUB20\4&361B340A&0 USB Root Hub C:\windows\system32\drivers\usbhub.sys 6.3.9600.17238 (winblue_gdr.140723-2018)
USB\ROOT_HUB20\4&361B340A&0 USB Root Hub C:\windows\system32\drivers\usbd.sys 6.3.9600.17195 (winblue_gdr.140530-1506)
ACPI\PNP0C09\1
USB\ROOT_HUB20\4&361B340A&1 USB Root Hub C:\windows\INF\usbport.inf
USB\ROOT_HUB20\4&361B340A&1 USB Root Hub C:\windows\system32\drivers\usbhub.sys 6.3.9600.17238 (winblue_gdr.140723-2018)
USB\ROOT_HUB20\4&361B340A&1 USB Root Hub C:\windows\system32\drivers\usbd.sys 6.3.9600.17195 (winblue_gdr.140530-1506)
THE PROBLEM
I would like to add an extra column with driver version.
How can I find the driver version and list it next to the name column? Thank you!
EDIT
The code below parses driver version from the INF file.
I am not experienced with PowerShell, so how can I used this bit of information and incorporate it with the above code to add an extra column and list driver version; preferably, next to Name column.
$pattern = 'DriverVer\s*=\s*(?:\d+/\d+/\d+,)?(.*)'
Select-String -Pattern $pattern -Path $path |
select -Expand Matches -First 1 |
% { $_.Groups[1].Value }
# $path = the INF file

Adding a new column is relatively simple. The columns are listed because they are properties of the object in the array. So right now we have object that are built like this (the driver line is similar, and then we pipe all remaining files into this, line breaks added for ease of reading):
[PSCustomObject]#{
'HardwareID' = $HardwareID
'Name' = $Name
'Files' = $_
'FileVersion' = If(Test-Path $_.Trim()){[System.Diagnostics.FileVersionInfo]::GetVersionInfo($_.trim()).FileVersion}Else{'File Not Found'}
}
From that we get the four columns 'HardwareID, 'Name', 'Files', and 'FileVersion'. If you want to add a column you can simply insert another property. In this case you want to add a new column next to Name that has the version of the driver for each item. We can simply re-purpose the line that currently outputs the driver file path (with missing version number) for each item:
[PSCustomObject]#{'HardwareID' = $HardwareID;'Name' = $Name; 'Files' = $Driver; 'FileVersion' = If(Test-Path $_.Trim()){[System.Diagnostics.FileVersionInfo]::GetVersionInfo($_.trim()).FileVersion}}
With that line we can just remove the bit in there that tries to list anything in the 'FileVersion' column, and add a new one in there for 'DriverVersion' using the code that you supplied (with some minor modifications). We're going to move the $Pattern = line to be the first line of the script, since we don't need to re-assign that for each iteration of the loop. Then we are going to assign the rest of your code to the new 'DriverVersion' property/column, and wrap it in a If($Driver){} statement, so that it doesn't throw errors for entries that don't have a driver listed. So that now looks like this:
[PSCustomObject]#{
'HardwareID' = $HardwareID
'Name' = $Name
'DriverVersion' = If($Driver){Select-String -Pattern $pattern -Path $driver | select -Expand Matches -First 1 |%{ $_.Groups[1].Value }}else{''}
'Files' = $Driver
'FileVersion' = '' }
Now, since we added that property to that line, we need to update the next line as well so that it includes that property as well (albeit with a blank value):
$Files | ForEach{ [PSCustomObject]#{'HardwareID' = $HardwareID;'Name' = $Name; 'DriverVersion'= ''; 'Files' = $_; 'FileVersion' = If(Test-Path $_.Trim()){[System.Diagnostics.FileVersionInfo]::GetVersionInfo($_.trim()).FileVersion}Else{'File Not Found'}}}
The updated code as a whole would look something like:
$pattern = 'DriverVer\s*=\s*(?:\d+/\d+/\d+,)?(.*)'
(Get-Content C:\Path\To\File.txt) -join "`r`n" -Split "(?m)^(?=\S)" |
Where{$_} |
ForEach{
Clear-Variable Files,Driver,Name,HardwareID,DriverVer -ea SilentlyContinue
$Files = #()
$HardwareID = ($_ -split "`r`n")[0].trim()
Switch -regex ($_ -split "`r`n"){
"^\s+Name:" {$Name = ($_ -split ':',2)[-1].trim();Continue}
"^\s+.:\\" {$Files += $_.trim();continue}
"^\s+Driver" {$Driver = [RegEx]::Matches($_,"(?<=Driver installed from )(.+?)(?= \[)").value;continue}
}
[PSCustomObject]#{
'HardwareID' = $HardwareID
'Name' = $Name
'DriverVersion' = If($Driver){Select-String -Pattern $pattern -Path $driver | select -Expand Matches -First 1 |%{ $_.Groups[1].Value }}else{''}
'Files' = $Driver
'FileVersion' = '' }
$Files | ForEach{ [PSCustomObject]#{'HardwareID' = $HardwareID;'Name' = $Name; 'DriverVersion'= ''; 'Files' = $_; 'FileVersion' = If(Test-Path $_.Trim()){[System.Diagnostics.FileVersionInfo]::GetVersionInfo($_.trim()).FileVersion}Else{'File Not Found'}}}
}|ft
This now outputs something along the lines of:
HardwareID Name DriverVersion Files FileVersion
---------- ---- ------------- ----- -----------
USB\ROOT_HUB20\4&361B340A&0 USB Root Hub 6.3.9600.17238 C:\windows\INF\usbport.inf
USB\ROOT_HUB20\4&361B340A&0 USB Root Hub C:\windows\system32\drivers\usbhub.sys 6.3.9600.17238 (winblue_gdr.140723-2018)
USB\ROOT_HUB20\4&361B340A&0 USB Root Hub C:\windows\system32\drivers\usbd.sys 6.3.9600.17195 (winblue_gdr.140530-1506)
ACPI\PNP0C09\1
USB\ROOT_HUB20\4&361B340A&1 USB Root Hub 6.3.9600.17238 C:\windows\INF\usbport.inf
USB\ROOT_HUB20\4&361B340A&1 USB Root Hub C:\windows\system32\drivers\usbhub.sys 6.3.9600.17238 (winblue_gdr.140723-2018)
USB\ROOT_HUB20\4&361B340A&1 USB Root Hub C:\windows\system32\drivers\usbd.sys 6.3.9600.17195 (winblue_gdr.140530-1506)
ACPI\PNP0C09\2
USB\ROOT_HUB20\4&361B340A&2 6.3.9600.16384 C:\windows\INF\cmbatt.inf
USB\ROOT_HUB20\4&361B340A&2 C:\windows\system32\DRIVERS\CmBatt.sys 6.3.9600.16384 (winblue_rtm.130821-1623)
USB\ROOT_HUB20\4&361B340A&2 C:\windows\system32\DRIVERS\battc.sys 6.3.9600.16384 (winblue_rtm.130821-1623)
ACPI\PNP0C0A\2 Microsoft ACPI-Compliant Control Method Battery 6.3.9600.16384 C:\windows\INF\cmbatt.inf
ACPI\PNP0C0A\2 Microsoft ACPI-Compliant Control Method Battery C:\windows\system32\DRIVERS\CmBatt.sys 6.3.9600.16384 (winblue_rtm.130821-1623)
ACPI\PNP0C0A\2 Microsoft ACPI-Compliant Control Method Battery C:\windows\system32\DRIVERS\battc.sys 6.3.9600.16384 (winblue_rtm.130821-1623)

Related

Creating registry DWORD entry with PowerShell

I am trying to create registry entries for some time now.
Enviroment:
Offline environment (No Domain)
1 Windows Server 2012 R2
200 Windows 7 clients
The following entry should be created on several computers:
[HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\system]
"LocalAccountTokenFilterPolicy"=dword:00000001
For this I would like to use the following script
$Computers = Get-Content "C:\Scripts\Clients.txt"
$Path = "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"
$Name = "LocalAccountTokenFilterPolicy"
$PropertyType = "DWord"
$Value = 1
$results = foreach ($computer in $Computers) {
if (Test-Connection -ComputerName $computer -Count 1 -Quiet) {
try {
Set-ItemProperty -Path $path -Name $Name -Value $Value -Type $PropertyType -ErrorAction 'Stop'
$status = "Success"
} catch {
$status = "Failed"
}
} else {
$status = "Unreachable"
}
New-Object -TypeName PSObject -Property #{
'Computer' = $computer
'Status' = $status
}
}
$results | Export-Csv -NoTypeInformation -Path "./error.csv"
The script is executed, no error appears, but the entries were not created.
Where is my error?
Always try a simple 'Get' first, and then progress to testing lines before trying your 'Set'...
So you first test locally on a system, before your foreach invoke, on single system:
`Get-ItemProperty HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System -Name LocalAccountTokenFilterPolicy`
If you get a good response like this:
LocalAccountTokenFilterPolicy : 0
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Curre
ntVersion\Policies\System
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Curre
ntVersion\Policies
PSChildName : System
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry
Then you've got the right command to work with. Now change the 'Get' to a 'Set' and work out the values LOCALLY.
I think you are close, but missed something in your tests...
Do ONE at a time, locally, first in PowerShell ISE. Then invoke-command to that same single system, then one by one add your tests...
- Patrick Burwell, 25 year Windows and Linux SysAdmin

How do I turn this simple command into something that loops from a list of remote systems?

I received a request to run this on each of my systems which pulls a list of installed applications and outputs it into a text file. I then have to combine all of these things into something more readable which will take a while. I am learning Powershell and want to make this be executed from one system, pull from a list of servers in a text file and run this query from one place against all of the systems:
Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Format-Table –AutoSize > "$Env:userprofile\desktop\Installed Programs for $env:computername.txt"
I've started working on it but am thinking I am missing something to get this to work. I am currently piping this to a string to then output to a csv (I am open to suggestions). This is what I have so far.
# Computer running this script
$WhoAmI = $env:ComputerName
$ServerList = get-content -path "C:\scripts\ServerList.txt"
$Path = "C:\scripts\results"
foreach ($server in $ServerList)
{
$InstalledApps = Invoke-Command -ComputerName $server {Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* }
$Results += $InstalledApps |
Select-Object DisplayName, DisplayVersion, Publisher, InstallDate |
Out-String
}
Write-Host $InstallApps
# $InstallApps | export-csv "$Path\InstalledFiles.csv"
I currently am testing the functionality of the loop by just trying to get it to write to the screen. I only get a blank response.
You weren't getting output because you used the (undefined) variable $InstallApps instead of the variable $results.
With that said, I wouldn't recommend doing string concatenation in a loop. Something like this would be a more elegant approach:
Get-Content -Path 'C:\scripts\ServerList.txt' | ForEach-Object {
$server = $_
Invoke-Command -ComputerName $server -ScriptBlock {
Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*
} | Select-Object #{n='Server';e={$server}}, DisplayName, DisplayVersion,
Publisher, InstallDate
} | Export-Csv 'C:\scripts\results\InstalledPrograms.csv' -NoType
I kind of figured it out. Rested eyes on a fresh day. Some mistakes in what I am writing out, etc. I am open to improvements if anyone has anything to contribute.
EDIT: I got it working mostly with the following but the output is messy. Open to suggestions.
# Computer running this script
$ServerList = get-content -path "C:\scripts\ServerList.txt"
$Path = "C:\scripts\results"
foreach ($server in $ServerList)
{
$Results += "Results for $server"
$InstalledApps = Invoke-Command -ComputerName $server -ScriptBlock {Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* }
$Results += $InstalledApps |
Select DisplayName, DisplayVersion, Publisher, InstallDate |
Out-String
}
# Write-Host $Results
$Results | out-file -filepath "$Path\InstalledPrograms.txt" -width 200

Powershell Parse Text File

I am trying to parse this text file. It would have been easy to parse into a table like at the end, if there was a patter.
And I'm new to Powershell, so not sure how to tackle this problem.
Any help is appreciated.
USB\ROOT_HUB20\4&361B340A&0
Name: USB Root Hub
Driver installed from C:\windows\INF\usbport.inf [ROOTHUB.Dev]. 2 file(s) used by driver:
C:\windows\system32\drivers\usbhub.sys
C:\windows\system32\drivers\usbd.sys
ACPI\PNP0C09\1
USB\ROOT_HUB20\4&361B340A&1
Name: USB Root Hub
Driver installed from C:\windows\INF\usbport.inf [ROOTHUB.Dev]. 2 file(s) used by driver:
C:\windows\system32\drivers\usbhub.sys
C:\windows\system32\drivers\usbd.sys
ACPI\PNP0C09\2
USB\ROOT_HUB20\4&361B340A&2
Driver installed from C:\windows\INF\cmbatt.inf [CmBatt_Inst]. 2 file(s) used by driver:
C:\windows\system32\DRIVERS\CmBatt.sys
C:\windows\system32\DRIVERS\battc.sys
ACPI\PNP0C0A\2
Name: Microsoft ACPI-Compliant Control Method Battery
Driver installed from C:\windows\INF\cmbatt.inf [CmBatt_Inst]. 2 file(s) used by driver:
C:\windows\system32\DRIVERS\CmBatt.sys
C:\windows\system32\DRIVERS\battc.sys
Output
HardwareID Name File(s)
---------- ---- -------
USB\ROOT_HUB20... USB Root Hub C:\windows\INF\usbport.inf
USB\ROOT_HUB2... USB Root Hub C:\windows\system32\drivers\usbhub.sys
USB\ROOT_HUB20.. USB Root Hub C:\windows\system32\drivers\usbd.sys
ACPI\PNP0C09\1 C:\windows\INF\machine.inf
ACPI\PNP0C0A\1 Microsoft AC... C:\windows\INF\cmbatt.inf
Microsoft AC... C:\windows\system32\DRIVERS\CmBatt.sys
RegEx can make fairly simple work of this. Import the file, join it together so that it's a single multiline string, then split it on lines that don't start with space. That gets you individual records. Then split those on new line characters, and parse each line depending on what's in it. Again, RegEx will help define each line is. This code will output an object for each section, with 4 properties, HardwareID, Name, Driver, and Files. The Files property is an array of files.
(Get-Content C:\Path\To\File.txt) -join "`r`n" -Split "(?m)^(?=\S)" |
Where{$_} |
ForEach{
Clear-Variable Files,Driver,Name,HardwareID
$Files = #()
$HardwareID = ($_ -split "`r`n")[0].trim()
Switch -regex ($_ -split "`r`n"){
"^\s+Name:" {$Name = ($_ -split ':',2)[-1].trim();Continue}
"^\s+.:\\" {$Files += $_.trim();continue}
"^\s+Driver" {$Driver = [RegEx]::Matches($_,"(?<=Driver installed from )(.+?)(?= \[)").value;continue}
}
[PSCustomObject]#{'HardwareID' = $HardwareID;'Name' = $Name; 'Driver' = $Driver; 'Files' = $Files}
}
So that will output something like:
HardwareID Name Driver Files
---------- ---- ------ -----
USB\ROOT_HUB20\4&361B340A&0 USB Root Hub C:\windows\INF\usbport.inf {C:\windows\system32\drivers\usbhub.sys, C:\windows\system32\drivers\usbd.sys}
ACPI\PNP0C09\1 {}
USB\ROOT_HUB20\4&361B340A&1 USB Root Hub C:\windows\INF\usbport.inf {C:\windows\system32\drivers\usbhub.sys, C:\windows\system32\drivers\usbd.sys}
ACPI\PNP0C09\2 {}
USB\ROOT_HUB20\4&361B340A&2 C:\windows\INF\cmbatt.inf {C:\windows\system32\DRIVERS\CmBatt.sys, C:\windows\system32\DRIVERS\battc.sys}
ACPI\PNP0C0A\2 Microsoft ACPI-Compliant Control Method Battery C:\windows\INF\cmbatt.inf {C:\windows\system32\DRIVERS\CmBatt.sys, C:\windows\system32\DRIVERS\battc.sys}
Edit: Ok, I'm going to assume that you didn't really try to modify what I had here since adding the Drivers to the list of other files should be really simple by changing $Driver = to $Files += just like the line above it to add that file to the array of files as well.Although, it looks like you want a line for each file, including the driver file, so maybe having an array for the files isn't best suited for you. What you could do is output an object for the driver file, and then an object for each of the supporting files, and you could get your version number for each on the fly as you make your objects. So you could replace the [PSCustomObject] line above with this:
[PSCustomObject]#{'HardwareID' = $HardwareID;'Name' = $Name; 'Files' = $Driver; 'FileVersion' = If(Test-Path $_.Trim()){[System.Diagnostics.FileVersionInfo]::GetVersionInfo($_.trim()).FileVersion}}
$Files | ForEach{ [PSCustomObject]#{'HardwareID' = $HardwareID;'Name' = $Name; 'Files' = $_; 'FileVersion' = If(Test-Path $_.Trim()){[System.Diagnostics.FileVersionInfo]::GetVersionInfo($_.trim()).FileVersion}Else{'File Not Found'}}}
This would output as:
HardwareID Name Files FileVersion
---------- ---- ----- -----------
USB\ROOT_HUB20\4&361B340A&0 USB Root Hub C:\windows\INF\usbport.inf
USB\ROOT_HUB20\4&361B340A&0 USB Root Hub C:\windows\system32\drivers\usbhub.sys 6.3.9600.17238 (winblue_gdr.140723-2018)
USB\ROOT_HUB20\4&361B340A&0 USB Root Hub C:\windows\system32\drivers\usbd.sys 6.3.9600.17195 (winblue_gdr.140530-1506)
ACPI\PNP0C09\1
USB\ROOT_HUB20\4&361B340A&1 USB Root Hub C:\windows\INF\usbport.inf
USB\ROOT_HUB20\4&361B340A&1 USB Root Hub C:\windows\system32\drivers\usbhub.sys 6.3.9600.17238 (winblue_gdr.140723-2018)
USB\ROOT_HUB20\4&361B340A&1 USB Root Hub C:\windows\system32\drivers\usbd.sys 6.3.9600.17195 (winblue_gdr.140530-1506)
ACPI\PNP0C09\2
USB\ROOT_HUB20\4&361B340A&2 C:\windows\INF\cmbatt.inf
USB\ROOT_HUB20\4&361B340A&2 C:\windows\system32\DRIVERS\CmBatt.sys 6.3.9600.16384 (winblue_rtm.130821-1623)
USB\ROOT_HUB20\4&361B340A&2 C:\windows\system32\DRIVERS\battc.sys 6.3.9600.16384 (winblue_rtm.130821-1623)
ACPI\PNP0C0A\2 Microsoft ACPI-Compliant Control Method Battery C:\windows\INF\cmbatt.inf
ACPI\PNP0C0A\2 Microsoft ACPI-Compliant Control Method Battery C:\windows\system32\DRIVERS\CmBatt.sys 6.3.9600.16384 (winblue_rtm.130821-1623)
ACPI\PNP0C0A\2 Microsoft ACPI-Compliant Control Method Battery C:\windows\system32\DRIVERS\battc.sys 6.3.9600.16384 (winblue_rtm.130821-1623)

Powershell - query servers with roles installed

I have a txt file with all the server names.
I want to query each server in the txt to see if RDS is installed. So far it works fine. But the export needs to have servername and the installed role. how can I pass the servername to the output file.
At the moment it is querying but only the installed. so with a 100 servers who really can tell which server has it installed and which doesnt>>>> PLEase help
Import-Module ServerManager
Get-Content W:\AllWindows.txt | ForEach-Object {Get-WindowsFeature -Name Remote-Desktop-Services} | Format-Table -Auto -wrap | Out-File -FilePath $csvfile
Try something like this:
Get-Content W:\AllWindows.txt | ForEach-Object { #(($_), (Get-WindowsFeature -Name Remote-Desktop-Services)) } | Format-Table -Auto -wrap | Out-File -FilePath $csvfile
I have worked it out:
Get-Content W:\windows2012.csv | Foreach-Object {{Get-WindowsFeature | where-object {$_.Installed -eq $True} | Export-Csv -Path "W:\output\$_.txt"}

Comparing Registry Keys on Two Servers using Powershell

I would like to compare two Servers registry keys to make sure they both match.
Something simple like this was the initial plan:
$remote1 = (invoke-command -computername hostname `
{Get-ItemProperty "HKLM:SOFTWARE\VENDOR\APP\SUBFOLDER"})
$local1 = (invoke-command `
{Get-ItemProperty "HKLM:SOFTWARE\VENDOR\APP\SUBFOLDER"})
$compare1 = Compare-Object $local1 $remote1
That works great for one single specified key but I have multiple keys with sub folders. I can't provide a list of the ones I want to check (and loop round) as I want to make sure nothing new has been added. So I was drawn down this route to get all the keys under a specified branch in the Registry (to get me a list):
$local1 = Get-ChildItem HKLM:SOFTWARE\MICROSOFT\DirectShow -Recurse `
-ErrorAction SilentlyContinue
So I now have an object that will tell me all the registry SUBFOLDERS on the server and I could then loop round using $local1.PSPath to give me all the paths but I noticed something in the object that was interesting:
$local1 | select -first 1 -prop *
This returns:
Property : {dbl3, dbl4, dbl5, dbl6...}
PSPath : A path
PSParentPath : A Parent Path
PSChildName : 0
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry
PSIsContainer : True
SubKeyCount : 0
View : Default
Handle : Microsoft.Win32.SafeHandles.SafeRegistryHandle
ValueCount : 8
Name : A Name
So the Object member "Property" contains what looks like an array of all the keys or is it a sub Object?
If it was a sub-object does it contain the keys values that I am looking to compare?
I could just snatch out the $local.Name member and loop round comparing using the code above and storing any differences but I just wondered if it would be more efficient to use the data that I already have if it contains the information I need?
I am hoping that someone could confirm that if I did:
$local1 = Get-ChildItem HKLM:SOFTWARE\MICROSOFT\DirectShow -Recurse `
-ErrorAction SilentlyContinue
$remote1 (invoke-command -computername remoteserver1 `
{Get-ChildItem HKLM:SOFTWARE\MICROSOFT\DirectShow -Recurse `
-ErrorAction SilentlyContinue})
When I do the compare am I actually comparing the keys and values match or just that keys exist? :
Compare-Object $local1 $remote1
To cut a long story short, I think I have all the data I need to compare that the Registry key values match (by running this):
$local1
Returns (extract):
Hive: HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\DirectShow
Name Property
---- --------
Debug
DoNotUse
DoNotUseDrivers32
Preferred {00001602-0000-0010-8000-00aa00389b71} : {E1F1A0B8-BEEE-490D-BA7C-066C40B5E2B9}
{e06d8032-db46-11cf-b4d1-00805f6cbbea} : {E1F1A0B8-BEEE-490D-BA7C-066C40B5E2B9}
{00000160-0000-0010-8000-00aa00389b71} : {2eeb4adf-4578-4d10-bca7-bb955f56320a}
{41564D57-0000-0010-8000-00AA00389B71} : {82d353df-90bd-4382-8bc2-3f6192b76e34}
{e06d8026-db46-11cf-b4d1-00805f6cbbea} : {212690FB-83E5-4526-8FD7-74478B7939CD}
Am I correct and does anyone know how to access individual items from the $local1 object? Taking the example above, what is a Hive? Say I wanted the "00001602-0000-0010-8000-00aa00389b71" value how would I get it from the $local1 object.
A point to note that the servers are running Powershell 2 while I am testing on Powershell 4 (I can't test on a Production server). I mention this as running $local1 on the v2 servers I get a different output in that the properties do not seem to be in pairs.
Hive: HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\DirectShow
SKC VC Name Property
--- -- ---- --------
0 0 Debug {}
0 0 DoNotUse {}
0 0 DoNotUseDrivers32 {}
0 10 Preferred {{00000050-0000-0010-8000-00AA00389B71}, {e436eb80-524f-11ce-9f53-0020af0ba77...
Is this case where the v4 objects will do what I want but the v2 won't?
Well fought my way back through the tumble-weed on this one to answer my own question. I had to assume that PowerShell V2 did not contain the key values as I couldn't find a way of extracting them so to be sure I was performing a comparison of the keys and key values I went with this:
foreach ( $KeyPath in $RegPath )
{
$_Path = (invoke-command {Get-ChildItem $KeyPath -Recurse -ErrorAction SilentlyContinue})
ForEach ($key in $_Path)
{
$local = #()
$remote = #()
ForEach ( $Property in $key.Property )
{
if ( $Key.Name -like "*HKEY_LOCAL_MACHINE*" )
{
$KeyPath = [regex]::Replace($key.Name,[regex]"HKEY_LOCAL_MACHINE\\","HKLM:")
} elseif ( $Key.Name -like "*HKEY_CURRENT_USER*" ) {
$KeyPath = [regex]::Replace($key.Name,[regex]"HKEY_CURRENT_USER\\","HKCU:")
} else {
Write-Host "I was unable to set the Registry Hive path, exiting........"
exit 1
}
$lentry = (invoke-command {Get-ItemProperty -Path $KeyPath -Name $Property})
$lfound = New-Object -TypeName PSObject
$lfound | Add-Member -Type NoteProperty -Name Path -Value $KeyPath
$lfound | Add-Member -Type NoteProperty -Name Name -Value $Property
$lfound | Add-Member -Type NoteProperty -Name Data -Value $lentry.$Property
$local += $lfound
$rentry = (invoke-command -computername remoteservername -Script {Get-ItemProperty -Path $args[0] -Name $args[1]} -Args $KeyPath, $Property -ErrorVariable errmsg 2>$null)
if ( $errmsg -like "*does not exist at path*" )
{
$Value = "KEY IS MISSING"
} else {
$Value = $rentry.$Property
}
$rfound = New-Object -TypeName PSObject
$rfound | Add-Member -Type NoteProperty -Name Path -Value $KeyPath
$rfound | Add-Member -Type NoteProperty -Name Name -Value $Property
$rfound | Add-Member -Type NoteProperty -Name Data -Value $Value
$remote += $rfound
}
$compare = Compare-Object -ReferenceObject $local -DifferenceObject $remote -Property Path,Name,Data
$results += $compare
}
}
You then have all the results in an $results object that can be worked on. I used the object in an HTML table similar to this Powershell Hash Table to HTML.
I think it would have been much simpler in PowerShell version four with no need to build $lfound and $rfound.
In powershell v4 you can just export the registry keys at whatever point you want and then use the following to compare them:
$badReg = Get-content C:\Data\badReg.reg
$goodReg = Get-ChildItem C:\Data\goodReg.reg
$results = Compare-Object -ReferenceObject $goodReg -DifferenceObject $badReg

Resources