Get serial numbers for installed software - sccm

Is it possible to get serial numbers from installed software (Adobe, Autodesk, VMWare etc.)?
From WMI I can get only MS keys - OS/Office. Is there any way to receive it from SCCM or AD?

There's no simple way that I know of. It would depend on each application, if you're lucky and they store the information in the registry then you can add that to software inventory, but if it's in an encrypted key file (as I suspect Adobe uses) then you're pretty much out of luck.

For non REG-BINARY type I use this script which reads information from csv file. So I don't need any decode manipulations.
$csv = Import-CSV "\\fs-lv-01\users$\username\Desktop\PowerShell\TSTArray.csv"
$resultsarray = #()
$target = $env:COMPUTERNAME
$hklm = "HKLM:"
$join_path = $hklm, $regPath -join "\"
Foreach ($Registry in $csv)
{
$regPath = $Registry.regPath
$regValue = $Registry.regValue
$join_path = $hklm, $regPath -join "\"
If ($Registry.regPath -ne 0)
{
$data = (Get-ItemProperty -Path $join_path -name $regValue -ErrorAction SilentlyContinue).$regValue
IF ($data -ne $null)
{
$obj = New-Object Object
$obj | Add-Member Noteproperty Computer -value $target
$obj | Add-Member Noteproperty ProductKey -value $data
$obj
$resultsarray += $obj
}
}
}
$resultsarray | Export-Csv "\\fs-lv-01\users$\username\Desktop\PowerShell\TSTSerialNumbers.csv" -NoTypeInformation
The same idea for Microsoft products:
$csv = Import-CSV "\\fs-lv-01\users$\username\Desktop\PowerShell\MSArray.csv"
$resultsarray = #()
$hklm = 2147483650
$target = $env:COMPUTERNAME
Foreach ($Registry in $csv)
{
$regPath = $Registry.regPath
$regValue = $Computer_name.regValue
If ($Registry.regPath -ne 0)
{
$productKey = $null
$win32os = $null
$wmi = [WMIClass]"\\$target\root\default:stdRegProv"
$data = $wmi.GetBinaryValue($hklm,$regPath,$regValue)
$binArray = ($data.uValue)[52..66]
$charsArray = "BCDFGHJKMPQRTVWXY2346789"
## decrypt base24 encoded binary data
for ($i = 24; $i -ge 0; $i--) {
$r = 0
for ($j = 14; $j -ge 0; $j--) {
$r = ($r * 256) -bxor $binArray[$j]
$binArray[$j] = [math]::Floor([double]($r/24))
$r = $r % 24
}
$ProductKey = $charsArray[$r] + $ProductKey
if (($i % 5) -eq 0 -and $i -ne 0) {
$ProductKey = "-" + $ProductKey
}
}
$win32os = Get-WmiObject Win32_OperatingSystem -computer $target
$obj = New-Object Object
$obj | Add-Member Noteproperty Computer -value $target
$obj | Add-Member Noteproperty Caption -value $win32os.Caption
$obj | Add-Member Noteproperty OSArch -value $win32os.OSArchitecture
$obj | Add-Member Noteproperty ProductKey -value $productKey
$obj
$resultsarray += $obj
}
}
$resultsarray | Export-Csv "\\fs-lv-01\users$\username\Desktop\PowerShell\SerialNumbers.csv" -NoTypeInformation
Both scripts are tested, but I want also to get Adobe keys.. I have information about how to decode Adobe serials, but I need to test it on licensed version.

Related

PowerShell script too slow

Need help with speeding up the script as it is going to be run against 10-20K servers. Currently tested on 4K servers and took almost 6 hours. Tried running it asjob (One parent job and 4000 childjobs, it runs fine and a lot faster but the parent job gets stuck in "running" state forever. It is because one of the childjobs stays in "Notstarted" state. Not sure how to fix that.
######################################################################################
$today = Get-Date
$path = (Get-Location).Path
$path += "\"
$date = Get-Date -uformat "%Y%m%d%H%M"
$Inputfile = $path + "Computers.txt"
$outfile = $path + "Report\" + "Certificate_Report_$date.csv"
$transcript = $path + "Logs\" + "Transcript_$date.log"
Start-Transcript $transcript
$computers = gc $Inputfile
Foreach ($c in $computers){
$cert = Invoke-Command -ComputerName $c -ScriptBlock{Get-ChildItem Cert:\localmachine -Recurse} -ErrorVariable issue -ErrorAction Continue
If ($issue){
$Connection = $Error[0].FullyQualifiedErrorId
$obj1 = New-Object Psobject
$Obj1 | Add-Member -MemberType NoteProperty -Name Server -Value $c
$Obj1 | Add-Member -MemberType NoteProperty -Name Serverconnection -Value $Connection
#$report += $obj1
$obj1 | Export-Csv $outfile -NoTypeInformation -Append -force
}
Else{$Connection = "Success"}
Foreach ($cer in $cert){
if($cer.Thumbprint -ne $null){
$obj = New-Object Psobject
$Obj | Add-Member -MemberType NoteProperty -Name Server -Value $c
$Obj | Add-Member -MemberType NoteProperty -Name Serverconnection -Value $Connection
$Obj | Add-Member -MemberType NoteProperty -Name PsParentpath -Value $Cer.PsParentpath
$Obj | Add-Member -MemberType NoteProperty -Name Subject -Value $Cer.Subject
$Obj | Add-Member -MemberType NoteProperty -Name Thumbprint -Value $Cer.Thumbprint
$Obj | Add-Member -MemberType NoteProperty -Name DnsNamelist -Value $Cer.DNSNamelist
$Obj | Add-Member -MemberType NoteProperty -Name FriendlyName -Value $Cer.FriendlyName
$Obj | Add-Member -MemberType NoteProperty -Name Issuer -Value $Cer.Issuer
$Obj | Add-Member -MemberType NoteProperty -Name Valid_From -Value $Cer.NotBefore
$Obj | Add-Member -MemberType NoteProperty -Name Expiration_Date -Value $Cer.NotAfter
if ($cer.NotAfter -lt $today){
$status = "Expired"
}
Else{$status = "Valid"}
$Obj | Add-Member -MemberType NoteProperty -Name Cert_Status -Value $status
$obj | Export-Csv $outfile -NoTypeInformation -Append
}
}
}
Stop-Transcript
The only obvious optimization that comes to mind (without parallelizing the remote queries) is to avoid | Add-Member and use [pscustomobject] syntax for the result objects:
$today = Get-Date
$date = Get-Date -uformat "%Y%m%d%H%M"
$Inputfile = (Resolve-Path "Computers.txt").Path
$outfile = (Resolve-Path "Report\Certificate_Report_$date.csv").Path
$transcript = (Resolve-Path "Logs\Transcript_$date.log").Path
Start-Transcript $transcript
$computers = gc $Inputfile
Foreach ($c in $computers) {
$cert = Invoke-Command -ComputerName $c -ScriptBlock { Get-ChildItem Cert:\localmachine -Recurse } -ErrorVariable issue -ErrorAction Continue
If ($issue) {
$Connection = $Error[0].FullyQualifiedErrorId
$obj1 = [pscustomobject]#{
Server = $c
Serverconnection = $Connection
} | Export-Csv $outfile -NoTypeInformation -Append -force
}
Else {
$Connection = "Success"
}
Foreach ($cer in $cert) {
if ($null -ne $cer.Thumbprint) {
[pscustomobject]#{
Server = $c
Serverconnection = $Connection
PsParentpath = $Cer.PsParentpath
Subject = $Cer.Subject
Thumbprint = $Cer.Thumbprint
DnsNamelist = $Cer.DNSNamelist
FriendlyName = $Cer.FriendlyName
Issuer = $Cer.Issuer
Valid_From = $Cer.NotBefore
Expiration_Date = $Cer.NotAfter
Cert_Status = if ($cer.NotAfter -lt $today) { "Expired" } else { "Valid" }
} | Export-Csv $outfile -NoTypeInformation -Append
}
}
}
Stop-Transcript
As Lee_Dailey mentions, you might also want to try offloading parallel execution of the remoting commands to Invoke-Command completely, by passing it all the computer names up front:
Invoke-Command -ComputerName $computers -ScriptBlock {Get-ChildItem Cert:\localmachine -Recurse} -ErrorAction Continue |For-EachObject {
# Process the results here
}
If you want help troubleshooting using background jobs, please post the code with which you have problems :)
Script posted in my question took almost 6 hours to do the same thing this modified version does in under 30 mins. Grateful for the help on this post. Final script below:
$today = Get-Date
$date = Get-Date -uformat "%Y%m%d%H%M"
$Inputfile = gc (Resolve-Path "Computers.txt").Path
$outfile = (Resolve-Path "Report\").Path + "Certificate_Report_$date.csv"
$transcript = (Resolve-Path "Logs\").Path + "Transcript_$date.log"
$failed = "Couldn't retrieve Data"
$IC_ScriptBlock = {Get-ChildItem Cert:\localmachine -Recurse}
$IC_Params = #{
ComputerName = $Inputfile
ScriptBlock = $IC_ScriptBlock
ErrorAction = 'SilentlyContinue'
}
$responding = Invoke-Command #IC_Params|ForEach-Object {
if ($null -ne $_.Thumbprint) {
[pscustomobject]#{
Server = $_.pscomputername
PsParentpath = $_.PsParentpath
Subject = $_.Subject
Thumbprint = $_.Thumbprint
DnsNamelist = $_.DNSNamelist
FriendlyName = $_.FriendlyName
Issuer = $_.Issuer
Valid_From = $_.NotBefore
Expiration_Date = $_.NotAfter
Cert_Status = if ($_.NotAfter -lt $today) { "Expired" } else { "Valid" }
} | Export-Csv $outfile -NoTypeInformation -Append
}
}
$not_responding = $Inputfile.Where({
$_ -notin $responding.Pscomputername -and "[$_]" -notin $responding.pscomputername
}).
foreach({
[pscustomobject]#{
Server = $_
PsParentpath = $failed
Subject = $failed
Thumbprint = $failed
DnsNamelist = $failed
FriendlyName = $failed
Issuer = $failed
Valid_From = $failed
Expiration_Date = $failed
Cert_Status = "NA"
} | Export-Csv $outfile -Append -NoTypeInformation
})

Connect to Remote Registry and report either i) the value ii) offline or iii) access denied

I am trying to connect to the registry of remote servers to check if some applications are installed and export the results to a csv. I need the script to report which servers have the application installed, which servers are online but deny access, and servers that are not on the network.
I have tried various approaches including using a try and catch block and using the exception errors, using a test-connection to identify offline servers, various loops etc. The code below identifies the servers that are offline successfully but reports the applications as not installed on servers I am denied access to even though they are online
$servers = "Server1","Server2","Server3"
$date = get-date
$domain = ([system.environment]::UserDomainName).tolower()
$i = 0
$report = "App Audit {0} {1:HHmm_dd-MM-yyyy}.csv" -f $domain,$date
# reg path for uninstall of 32 bit version of the various applications
$pk32App1 = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\App1"
$pk32App2 = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\App2"
# reg path for uninstall of 64 bit version of the various applications
$pk64app1 = "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\App1"
$pk64app2 = "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\App2"
foreach ($server in $servers) {
$i++
write-host "($i/$($servers.count))`t$server"
$obj = New-Object PSObject
$obj1 = New-Object PSObject
$array = #()
$array1 = #()
$app1 = ""
$app2 = ""
$error.Clear()
if (-not (Test-Connection $server -Count 2 -Quiet)){
$app1 = "Offline"
$app2 = "Offline"
} else {
$reg = [microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$server)
if (-not ($reg)){
$app1 = "Access Denied"
$app1 = "Access Denied"
$app2 = "Access Denied"
}
else {
# open subkey for 32 bit version of the various applications
$regpk32app1 = $reg.OpenSubKey($pk32app1)
$regpk32app2 = $reg.OpenSubKey($pk32app2)
# open subkey for 64 bit version of the various applications
$regpk64app1 = $reg.OpenSubKey($pk64app1)
$regpk64app2 = $reg.OpenSubKey($pk64app2)
$app1 = $($regpk32app1.GetValue("publisher")) 2>> $null
$app12 = $($regpk64app1.GetValue("publisher")) 2>> $null
if ($app1){$app1 = $app1}elseif ($app12){$app1 = $app12}else {$app1 = "Not Installed"}
$app21 = $($regpk32app2.GetValue("publisher")) 2>> $null
$app2 = $($regpk64app2.GetValue("publisher")) 2>> $null
if ($app21){$app2 = $app21}elseif ($app2){$app2 = $app2}else {$app2 = "Not Installed"}
}
}
$obj | Add-Member -MemberType NoteProperty -Name "Server" -Value $Server
$obj | Add-Member -MemberType NoteProperty -Name "app1" -Value $app1
$obj | Add-Member -MemberType NoteProperty -Name "app2" -Value $app2
$array += $obj
$array | select Server,app1,app2 | export-csv "$PWD\Output\$report" -NoTypeInformation -Append
}
Instead of the if (-not ($reg)){ code block, try the below code block:
$reg.OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall')
if(-not $?){
$app1 = "Access Denied"
$app2 = "Access Denied"
}
Full code:
$servers = "Server1", "Server2", "Server3"
$date = get-date
$domain = ([system.environment]::UserDomainName).tolower()
$i = 0
$report = "App Audit {0} {1:HHmm_dd-MM-yyyy}.csv" -f $domain, $date
# Registry path for uninstall of 32-bit version of the various applications
$pk32App1 = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\App1"
$pk32App2 = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\App2"
# Registry path for uninstall of 64 bit version of the various applications
$pk64app1 = "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\App1"
$pk64app2 = "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\App2"
foreach ($server in $servers) {
$i++
write-host "($i/$($servers.count))`t$server"
$obj = New-Object PSObject
$obj1 = New-Object PSObject
$array = #()
$array1 = #()
$app1 = ""
$app2 = ""
$error.Clear()
if (-not (Test-Connection $server -Count 2 -Quiet)){
$app1 = "Offline"
$app2 = "Offline"
}
else {
$reg = [microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine', $server)
$reg.OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall')
if(-not $?){
$app1 = "Access Denied"
$app2 = "Access Denied"
}
else {
# Open subkey for 32-bit version of the various applications
$regpk32app1 = $reg.OpenSubKey($pk32app1)
$regpk32app2 = $reg.OpenSubKey($pk32app2)
# Open subkey for 64-bit version of the various applications
$regpk64app1 = $reg.OpenSubKey($pk64app1)
$regpk64app2 = $reg.OpenSubKey($pk64app2)
$app1 = $($regpk32app1.GetValue("publisher")) 2>> $null
$app12 = $($regpk64app1.GetValue("publisher")) 2>> $null
if ($app1) {
$app1 = $app1
}
elseif ($app12) {
$app1 = $app12}
else {
$app1 = "Not Installed"
}
$app21 = $($regpk32app2.GetValue("publisher")) 2>> $null
$app2 = $($regpk64app2.GetValue("publisher")) 2>> $null
if ($app21) {
$app2 = $app21
}
elseif ($app2) {
$app2 = $app2
}
else {
$app2 = "Not Installed"
}
}
}
$obj | Add-Member -MemberType NoteProperty -Name "Server" -Value $Server
$obj | Add-Member -MemberType NoteProperty -Name "app1" -Value $app1
$obj | Add-Member -MemberType NoteProperty -Name "app2" -Value $app2
$array += $obj
$array | select Server,app1,app2 | export-csv "$PWD\Output\$report" -NoTypeInformation -Append
}

How to find Windows product Key if your sticker is damaged

I found a possible solution to the question here: http://youtu.be/71Vc9QiraQE
I am not a power shell user myself and cannot quite follow the code in the script.
It would be a useful script to help with reinstalls when operating systems have been upgraded. My problem is I would like to understand it more before using it or at least be told it looks harmless.
Here is the Script:
function Get-WindowsKey {
## function to retrieve the Windows Product Key from any PC
param ($targets = ".")
$hklm = 2147483650
$regPath = "Software\Microsoft\Windows NT\CurrentVersion"
$regValue = "DigitalProductId"
Foreach ($target in $targets) {
$productKey = $null
$win32os = $null
$wmi = [WMIClass]"\\$target\root\default:stdRegProv"
$data = $wmi.GetBinaryValue($hklm,$regPath,$regValue)
$binArray = ($data.uValue)[52..66]
$charsArray = "B","C","D","E","F","G","H","J","K","M","P","Q","­R","T","V","W","X","Y","2","3","4","5","6","7","8","9"
## decrypt base24 encoded binary data
For ($i = 24; $i -ge 0; $i--) {
$k = 0
For ($j = 14; $j -ge 0; $j--) {
$k = $k * 256 -bxor $binArray[$j]
$binArray[$j] = [math]::truncate($k / 24)
$k = $k % 24
}
$productKey = $charsArray[$k] + $productKey
If (($i % 5 -eq 0) -and ($i -ne 0)) {
$productKey = "-" + $productKey
}
}
$win32os = Get-WmiObject Win32_OperatingSystem -computer $target
$obj = New-Object Object
$obj | Add-Member Noteproperty Computer -value $target
$obj | Add-Member Noteproperty Caption -value $win32os.Caption
$obj | Add-Member Noteproperty CSDVersion -value $win32os.CSDVersion
$obj | Add-Member Noteproperty OSArch -value $win32os.OSArchitecture
$obj | Add-Member Noteproperty BuildNumber -value $win32os.BuildNumber
$obj | Add-Member Noteproperty RegisteredTo -value $win32os.RegisteredUser
$obj | Add-Member Noteproperty ProductID -value $win32os.SerialNumber
$obj | Add-Member Noteproperty ProductKey -value $productkey
$obj
}
}
Get-WindowsKey
The first command used in Windows PowerShell:
Set-ExecutionPolicy RemoteSigned
If the first script doesn't work, try this:
I was able to get my key using this, but I had to fix the script, changing the last line to:
function Get-WindowsKey {
# ...
}
Get-WindowsKey localhost
I think the YouTube video overdoes it a bit, you could simply cut and paste the contents of that into a PowerShell window without having to muck about with executionpolicy and importing the file.
Just open PowerShell, paste the below code into it and hit Enter a few times until it returns.
param ($targets = ".")
$hklm = 2147483650
$regPath = "Software\Microsoft\Windows NT\CurrentVersion"
$regValue = "DigitalProductId4"
Foreach ($target in $targets) {
$productKey = $null
$win32os = $null
$wmi = [WMIClass]"\\$target\root\default:stdRegProv"
$data = $wmi.GetBinaryValue($hklm,$regPath,$regValue)
$binArray = ($data.uValue)[52..66]
$charsArray = "B","C","D","E","F","G","H","J","K","M","P","Q","R","T","V","W","X","Y","2","3","4","5","6","7","8","9"
## decrypt base24 encoded binary data to characters.
For ($i = 24; $i -ge 0; $i--) {
$k = 0
For ($j = 14; $j -ge 0; $j--) {
$k = $k * 256 -bxor $binArray[$j]
$binArray[$j] = [math]::truncate($k / 24)
$k = $k % 24
}
$productKey = $charsArray[$k] + $productKey
If (($i % 5 -eq 0) -and ($i -ne 0)) {
$productKey = "-" + $productKey
}
}
$win32os = Get-WmiObject Win32_OperatingSystem -computer $target
$obj = New-Object Object
$obj | Add-Member Noteproperty Computer -value $target
$obj | Add-Member Noteproperty Caption -value $win32os.Caption
$obj | Add-Member Noteproperty CSDVersion -value $win32os.CSDVersion
$obj | Add-Member Noteproperty OSArch -value $win32os.OSArchitecture
$obj | Add-Member Noteproperty BuildNumber -value $win32os.BuildNumber
$obj | Add-Member Noteproperty RegisteredTo -value $win32os.RegisteredUser
$obj | Add-Member Noteproperty ProductID -value $win32os.SerialNumber
$obj | Add-Member Noteproperty ProductKey -value $productkey
$obj
}
I highly recommend backing up the hard drive (or replacing it entirely) in case the script hasn't decrypted it properly. It would kind of suck to go through all this to find it's wrong and you've just wiped your previous OS.
Trying to explain what the code does.
The script is reading the registry value Software\Microsoft\Windows NT\CurrentVersion\DigitalProductId and extracts the relevant part
$wmi = [WMIClass]"\\$target\root\default:stdRegProv"
$data = $wmi.GetBinaryValue($hklm,$regPath,$regValue)
$binArray = ($data.uValue)[52..66]
As the comment in the code says, it then decodes ("decrypts") the value in the for loop. The registry value is base24 encoded.
$charsArray = "B","C","D","E","F","G","H","J","K","M","P","Q","R","T","V","W","X","Y","2","3","4","5","6","7","8","9"
For ($i = 24; $i -ge 0; $i--) {
$k = 0
For ($j = 14; $j -ge 0; $j--) {
$k = $k * 256 -bxor $binArray[$j]
$binArray[$j] = [math]::truncate($k / 24)
$k = $k % 24
}
$productKey = $charsArray[$k] + $productKey
If (($i % 5 -eq 0) -and ($i -ne 0)) {
$productKey = "-" + $productKey
}
}
And finally the code shows the product key along with several system values
$win32os = Get-WmiObject Win32_OperatingSystem -computer $target
$obj = New-Object Object
$obj | Add-Member Noteproperty Computer -value $target
$obj | Add-Member Noteproperty Caption -value $win32os.Caption
$obj | Add-Member Noteproperty CSDVersion -value $win32os.CSDVersion
$obj | Add-Member Noteproperty OSArch -value $win32os.OSArchitecture
$obj | Add-Member Noteproperty BuildNumber -value $win32os.BuildNumber
$obj | Add-Member Noteproperty RegisteredTo -value $win32os.RegisteredUser
$obj | Add-Member Noteproperty ProductID -value $win32os.SerialNumber
$obj | Add-Member Noteproperty ProductKey -value $productkey
$obj
Depending on whether it's a retail or enterprise version, the registry value may be found in either DigitalProductId or DigitalProductId4, e.g.
$regValue = "DigitalProductId"
vs
$regValue = "DigitalProductId4"
The code is wrong, you are getting the key decrypted wrong...
Look at $charsArray, no E and a couple others...
function Get-WindowsKey {
## function to retrieve the Windows Product Key from any PC
param ($targets = ".")
$hklm = 2147483650
$regPath = "Software\Microsoft\Windows NT\CurrentVersion"
$regValue = "DigitalProductId"
Foreach ($target in $targets) {
$productKey = $null
$win32os = $null
$wmi = [WMIClass]"\\$target\root\default:stdRegProv"
$data = $wmi.GetBinaryValue($hklm,$regPath,$regValue)
$binArray = ($data.uValue)[52..66]
$charsArray = "B","C","D","F","G","H","J","K","M","P","Q","­R","T","V","W","X","Y","2","3","4","6","7","8","9"
## decrypt base24 encoded binary data
For ($i = 24; $i -ge 0; $i--) {
$k = 0
For ($j = 14; $j -ge 0; $j--) {
$k = $k * 256 -bxor $binArray[$j]
$binArray[$j] = [math]::truncate($k / 24)
$k = $k % 24
}
$productKey = $charsArray[$k] + $productKey
If (($i % 5 -eq 0) -and ($i -ne 0)) {
$productKey = "-" + $productKey
}
}
$win32os = Get-WmiObject Win32_OperatingSystem -computer $target
$obj = New-Object Object
$obj | Add-Member Noteproperty Computer -value $target
$obj | Add-Member Noteproperty Caption -value $win32os.Caption
$obj | Add-Member Noteproperty CSDVersion -value $win32os.CSDVersion
$obj | Add-Member Noteproperty OSArch -value $win32os.OSArchitecture
$obj | Add-Member Noteproperty BuildNumber -value $win32os.BuildNumber
$obj | Add-Member Noteproperty RegisteredTo -value $win32os.RegisteredUser
$obj | Add-Member Noteproperty ProductID -value $win32os.SerialNumber
$obj | Add-Member Noteproperty ProductKey -value $productkey
$obj
}
}
$key = Get-WindowsKey
$key.productkey

Where-Object or Compare-Object which would be better?

This may be a philosophical question but I would like to know how the following 2 items differ, from a speed and efficiency perspective. In PowerShell I have 2 objects that look like this:
$ObjectA = #()
1..10 | foreach-object{
$obj = New-Object System.Object
$obj | Add-Member -Type NoteProperty -Name index -Value $_
$ObjectA += $obj
}
$ObjectB = #()
5..15 | foreach-0bject{
$obj = New-Object System.Object
$obj | Add-Member -Type NoteProperty -Name index -Value $_
$ObjectB += $obj
}
Now, I want to get the objects that exist in both. I can do it 1 of 2 ways.
Solution 1:
$ObjectA | foreach-object{
$ind = $_
$matching = $ObjectB | where {$_ -eq $ind}
if (![string]::IsNullOrEmpty($matching)){
##do stuff with the match
}
}
Solution 2:
$matches = Compare-Object $ObjectA $ObjectB -Property index | where {$_.SideIndicator -eq '=='} -PassThru
$matches | foreach-object {
##do stuff with the matches.
}
My question is, when my array of objects gets very large (30K+) which one is going to be a better solution from a performance perspective? I don't know how the Compare-Object cmdlet works internally so I really don't know. Or does it not matter?
Thanks in advance.
As #Knows Not Much has pointed out, Compare-Object usually offers better performance than iterating the collection and comparing objects yourself. But the other answer fails to use the -ExcludeDifferent parameter and instead iterates over the Compare-Object output. This means doing many useless string comparisons for the SideIndicator property. For optimal performance, and simpler code, just use -IncludeEqual and -ExcludeDifferent:
$ObjectA = #()
1..10000 | %{
$obj = New-Object System.Object
$obj | Add-Member -Type NoteProperty -Name index -Value $_
$ObjectA += $obj
}
$ObjectB = #()
1000..7000 | %{
$obj = New-Object System.Object
$obj | Add-Member -Type NoteProperty -Name index -Value $_
$ObjectB += $obj
}
# Iterating over the result of Compare-Object takes 2.6 seconds.
Measure-Command { $matches_where_eq = Compare-Object $ObjectA $ObjectB -Property index -IncludeEqual | where {$_.SideIndicator -eq '=='} ; echo $matches_where_eq.count }
# Using -IncludeEqual and -ExcludeDifferent takes 2.1 seconds (80% of previous).
Measure-Command { $matches_ed_ie = Compare-Object $ObjectA $ObjectB -Property index -ExcludeDifferent -IncludeEqual; echo $matches_ed_ie.Count }
Even if you take a dataset of size 10000 you can easily see that compare object is way way faster.
I modified your code to make it work on powershell 3.0
cls
$ObjectA = #()
1..10000 | %{
$obj = New-Object System.Object
$obj | Add-Member -Type NoteProperty -Name index -Value $_
$ObjectA += $obj
}
$ObjectB = #()
1000..7000 | %{
$obj = New-Object System.Object
$obj | Add-Member -Type NoteProperty -Name index -Value $_
$ObjectB += $obj
}
Measure-Command {
$count = 0
$matches = Compare-Object $ObjectA $ObjectB -Property index -IncludeEqual | where {$_.SideIndicator -eq '=='}
}
echo $matches.length
echo $matches.Count
Measure-Command {
$count = 0
$ObjectA | %{
$ind = $_
$matching = $ObjectB | where {$_.Index -eq $ind.Index}
if (![string]::IsNullOrEmpty($matching)){
$count = $count + 1
}
}
echo $count
}
The compare-object returns in less than 5 seconds .... but the other approach just gets stuck forever.
This will build a regex search out of one array and then perform a regex match against the other array.
Solution 3:
[regex]$RegMatch = '(' + (($ObjectA |foreach {[regex]::escape($_)}) –join "|") + ')'
$ObjectB -match $RegMatch
Might want to throw some logic at that to build the regex out of the smaller set of data and then run the larger set against it to speed things up, but I'm pretty sure this would be fastest.

Loop through all bindings configured in IIS with powershell

I'm looking for a way to go through all binding settings already configured in my IIS.
Im using this to work with the IIS in Powershell:
Import-Module WebAdministration
So far I was able to get the main required information i want:
$Websites = Get-ChildItem IIS:\Sites
My array $Websites is filled correctly and with the following command...
$Websites[2]
..I recieve this result:
Name ID State Physical Path Bindings
---- -- ----- ------------- --------------
WebPage3 5 D:\Web\Page3 http *:80:WebPage3
https *:443:WebPage3
Now here's the part I having a hard time with:
I want to check if the binding is correct. In order to do that I only need the binding. I tried:
foreach ($site in $Websites)
{
$site = $Websites[0]
$site | select-string "http"
}
Debugging that code shows me that $Site doesn't contain what I expected: "Microsoft.IIs.PowerShell.Framework.ConfigurationElement". I currently have no clue how to explicitly get to the binding information in order to to something like this (inside the foreach loop):
if ($site.name -eq "WebPage3" -and $site.Port -eq "80") {
#website is ok
}
else {
#remove all current binding
#add correct binding
}
Thank you for your help!
Solution:
Import-Module WebAdministration
$Websites = Get-ChildItem IIS:\Sites
foreach ($Site in $Websites) {
$Binding = $Site.bindings
[string]$BindingInfo = $Binding.Collection
[string]$IP = $BindingInfo.SubString($BindingInfo.IndexOf(" "),$BindingInfo.IndexOf(":")-$BindingInfo.IndexOf(" "))
[string]$Port = $BindingInfo.SubString($BindingInfo.IndexOf(":")+1,$BindingInfo.LastIndexOf(":")-$BindingInfo.IndexOf(":")-1)
Write-Host "Binding info for" $Site.name " - IP:"$IP", Port:"$Port
if ($Site.enabledProtocols -eq "http") {
#DO CHECKS HERE
}
elseif($site.enabledProtocols -eq "https") {
#DO CHECKS HERE
}
}
I don't know exactly what you are trying to do, but I will try. I see that you reference $Websites[2] which is webPage3.
You can do it like this:
$site = $websites | Where-object { $_.Name -eq 'WebPage3' }
Then when you look at $site.Bindings, you will realize that you need the Collection member:
$site.bindings.Collection
On my machine this returns this:
protocol bindingInformation
-------- ------------------
http *:80:
net.tcp 808:*
net.pipe *
net.msmq localhost
msmq.formatname localhost
https *:443:
And the test might then look like this:
$is80 = [bool]($site.bindings.Collection | ? { $_.bindingInformation -eq '*:80:' })
if ($is80) {
#website is ok
} else {
#remove all current binding
#add correct binding
}
I sent content of Collection to pipeline and filtere only objects where property bindingInformation is equal to desired value (change it). Then I cast it to [bool]. This will return $true if there is desired item, $false otherwise.
I found that if there were multiple bindings on a site then if I needed to script access to individual parts of the bindings otherwise I only got the first binding. To get them all I needed the script to be extended as below:
Import-Module WebAdministration
$Websites = Get-ChildItem IIS:\Sites
foreach ($Site in $Websites) {
$Binding = $Site.bindings
[string]$BindingInfo = $Binding.Collection
[string[]]$Bindings = $BindingInfo.Split(" ")
$i = 0
$header = ""
Do{
Write-Output ("Site :- " + $Site.name + " <" + $Site.id +">")
Write-Output ("Protocol:- " + $Bindings[($i)])
[string[]]$Bindings2 = $Bindings[($i+1)].Split(":")
Write-Output ("IP :- " + $Bindings2[0])
Write-Output ("Port :- " + $Bindings2[1])
Write-Output ("Header :- " + $Bindings2[2])
$i=$i+2
} while ($i -lt ($bindings.count))
}
I had something similar to the last answer, but this corrects to HTTPS sites and adds a bit more information that is useful.
Import-Module WebAdministration
$hostname = hostname
$Websites = Get-ChildItem IIS:\Sites
$date = (Get-Date).ToString('MMddyyyy')
foreach ($Site in $Websites) {
$Binding = $Site.bindings
[string]$BindingInfo = $Binding.Collection
[string[]]$Bindings = $BindingInfo.Split(" ")#[0]
$i = 0
$status = $site.state
$path = $site.PhysicalPath
$fullName = $site.name
$state = ($site.name -split "-")[0]
$Collection = ($site.name -split "-")[1]
$status = $site.State
$anon = get-WebConfigurationProperty -Filter /system.webServer/security/authentication/AnonymousAuthentication -Name Enabled -PSPath IIS:\sites -Location $site.name | select-object Value
$basic = get-WebConfigurationProperty -Filter /system.webServer/security/authentication/BasicAuthentication -Name Enabled -PSPath IIS:\ -location $site.name | select-object Value
Do{
if( $Bindings[($i)] -notlike "sslFlags=*"){
[string[]]$Bindings2 = $Bindings[($i+1)].Split(":")
$obj = New-Object PSObject
$obj | Add-Member Date $Date
$obj | Add-Member Host $hostname
$obj | Add-Member State $state
$obj | Add-Member Collection $Collection
$obj | Add-Member SiteName $Site.name
$obj | Add-Member SiteID $site.id
$obj | Add-member Path $site.physicalPath
$obj | Add-Member Protocol $Bindings[($i)]
$obj | Add-Member Port $Bindings2[1]
$obj | Add-Member Header $Bindings2[2]
$obj | Add-member AuthAnon $Anon.value
$obj | Add-member AuthBasic $basic.value
$obj | Add-member Status $status
$obj #take this out if you want to save to csv| export-csv "c:\temp\$date-$hostname.csv" -Append -notypeinformation
$i=$i+2
}
else{$i=$i+1}
} while ($i -lt ($bindings.count))
}

Resources