PowerShell script too slow - windows

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
})

Related

How to write all PowerShell screen output to .csv report file

Fig1 Fig2 While I know this is a similar to many other questions regarding this, however, I have been having a difficult time figuring out how to make what I see on the screen go to the output file. I'm using PowerShell Version 5.1.16299.1146. Fig1 image is what I see on the PS screen. I want the script to see if a particular file is present and if it is TRUE or FALSE, write the information to the .csv file. Fig2 image is what actually gets written to the .csv report. I want the computer Name, Results (TRUE/FALSE), and Users + LastWriteTime written to the .csv file if it is found in the user's AppDate location for each user on a particular machine.
Set-ExecutionPolicy Bypass
$javausers = #()
$env:COMPUTERNAME = HostName
$TestPath = "$env:userprofile\AppData\LocalLow\Sun\Java\Deployment\deployment.properties"
$TestResult = if ( $(Try { Test-Path $TestPath.trim() } Catch { $false }) ) { Write-Output "True - deployment.properties" } Else { Write-Output "False - Path not found" }
$users = Get-ChildItem c:\users
foreach ($user in $users)
{
$folder = "C:\users\" + $user + "$env:userprofile\AppData\LocalLow\Sun\Java\Deployment\deployment.properties"
if ( $(Try { Test-Path $TestPath.trim() } Catch { $false }) ) { Write-Output "True - deployment.properties" $users -join ','} Else { Write-Output "False - Path not found" $users-join ','}
}
$javauser = New-Object System.Object
$javauser | Add-Member -MemberType NoteProperty -Name "Computer Name" -Value $env:COMPUTERNAME
#$javauser | Add-Member -MemberType NoteProperty -Name "Java User" -Value $TestPath
$javauser | Add-Member -MemberType NoteProperty -Name "Results" -Value $TestResult
$javauser | Add-Member -MemberType NoteProperty -Name "Users" -Value $folder
#$javauser | Add-Member -MemberType NoteProperty -Name "Users" -Value $users
$javausers += $javauser
$javausers | Export-Csv -NoTypeInformation -Path "C:\Temp\JavaUsersList.csv" -Append
To read other users folders you'll need to RunsAsAdmin.
#Requires -RunAsAdministrator
## Q:\Test\2019\08\29\SO_57714265.ps1
Set-ExecutionPolicy Bypass
$env:COMPUTERNAME = HostName
$DeplPath = "AppData\LocalLow\Sun\Java\Deployment\deployment.properties"
$javausers = foreach ($User in Get-ChildItem C:\Users -Directory){
$folder = Join-Path $User.FullName $DeplPath
if (Test-Path $folder) {
$TestResult = "True - deployment.properties"
} Else {
$TestResult = "False - Path not found"
}
[PSCustomObject]#{
"Computer Name" = $env:COMPUTERNAME
"Results" = $TestResult
"Users" = $user.Name
}
}
$javausers
#$javausers | Export-Csv -NoTypeInformation -Path "C:\Temp\JavaUsersList.csv" -Append
Sample output:
Computer Name Results Users
------------- ------- -----
VBoxWin10 False - Path not found SomeOne
VBoxWin10 False - Path not found Public
VBoxWin10 True - deployment.properties LotPings

Errors with Get-ChildItem trying to scan network for all .mdb files

I am very new to using powershell and trying to execute a script that scans the entire network for all .mdb and .accdb files, for example, and generates a spreadsheet containing the data on them that I process elsewhere.
I put the sensistive data that I didnt want to provide in ()s
Here is my code:
#single threaded
import-module activedirectory
$arr = #()
$computers = Get-ADComputer -filter 'name -like "(employee computers)*"' | Select -Exp Name
foreach ($computer in $computers) {
Write-Host "Scanning" $computer "..."
gci \\$computer\c$\* -Include *.mdb, *.accdb -Recurse | ? {$_.PSIsContainer -eq $False} | % {
$obj = New-Object PSObject
$obj | Add-Member NoteProperty Directory $_.DirectoryName
$obj | Add-Member NoteProperty Name $_.Name
$obj | Add-Member NoteProperty FullName $_.FullName
$obj | Add-Member NoteProperty Size $_.Length
$obj | Add-Member NoteProperty CreationTime $_.CreationTime
$obj | Add-Member NoteProperty LastWriteTime $_.LastWriteTime
$arr += $obj
Write-Host "Scanning..."
}}
$arr | Export-CSV -notypeinformation '(path)\EmployeeDBs.csv'
This has been working pretty well so far, but for certain machines and/or directories on some machines I am receiving the following error messages:
Get-ChildItem : The specified network name is no longer available
[Get-ChildItem], IOException
+ FullyQualifiedErrorId : DirIOError,Microsoft.PowerShell.Commands.GetChildItemCommand
and
Get-ChildItem : An object at the specified path \\(employee computer)\c$ does not exist.
[Get-ChildItem], IOException
+ FullyQualifiedErrorId : ItemDoesNotExist,Microsoft.PowerShell.Commands.GetChildItemCommand
I have been googling around but havent had much luck in understanding these error messages. Would somebody be able to explain what the issues are?
I am thinking (hoping) that they are permissions problems because I am testing the scripts on my personal machine before I run them from the admin machine
Any insight is greatly appreciated!
EDIT: below is my edited code for asynchronous execution:
import-module activedirectory
$computers = Get-ADComputer -filter 'name -like "wa-150*"' | Select -Exp Name
Get-job | Remove-Job -Force
Remove-Item -path (path)\EmployeeDBs.txt
foreach ($computer in $computers) {
$scriptBlock = {gci \\$($args[0])\c$\Users\z*\Desktop\* -Include *.mdb, *.accdb -Recurse | ? {$_.PSIsContainer -eq $False} | % {
$obj = New-Object PSObject
$obj | Add-Member NoteProperty Directory $_.DirectoryName
$obj | Add-Member NoteProperty Name $_.Name
$obj | Add-Member NoteProperty Size $_.Length
$obj | Add-Member NoteProperty CreationTime $_.CreationTime
$obj | Add-Member NoteProperty LastWriteTime $_.LastWriteTime
Write-Output -InputObject $obj
}
}
while ((Get-Job -State Running).Count -ge 20) {
Write-Host "Full - Waiting ... "
Start-Sleep -Seconds 5;
}
Start-Job -name $computer -ScriptBlock $scriptBlock -ArgumentList $computer
#Invoke-Command -ScriptBlock $scriptBlock -ArgumentList $computer
}
Get-Job | Wait-Job | Receive-Job | Out-File -Append -FilePath '(path)\EmployeeDBs.txt'
Write-Host "Done"

Get serial numbers for installed software

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.

Output on the basis of folder and file name

I have some directories structure like below, where the last folder name will be the current date changing everyday, as below:
D:\data\Backup\WINDOWSDATA\18-03-2015
D:\data\Backup\LINUXDATA\18-03-2015
D:\data\Backup\UBUNTUDATA\18-03-2015
Under each date folder (18-03-2015) there will be maximum four .dat files having different time stamps in their names, as given below:
BKP_DS_FETCHER_6AM.dat
BKP_DS_FETCHER_10AM.dat
BKP_DS_FETCHER_2PM.dat
BKP_DS_FETCHER_6PM.dat
I am trying to generate following results in an output.txt file on the basis of simple logic that is if .dat file is there for a particular time, there should come Success otherwise Failed in output.txt for example as below:
output.txt:
FOLDER_NAME 6AM 10AM 2PM 6PM
WINDOWSDATA Success Failed Success Success
LINUXDATA Success Success Failed Success
UBUNTUDATA Failed Success Success Success
Please can somebody help me show the way to achieve it (in Batch or Powershell) ?
Push-Location D:\data\Backup
$todayDir = Get-Item (get-date -Format 'dd-MM-yyyy') -ErrorAction SilentlyContinue
if($todayDir)
{
Push-Location $todayDir
$logResult = #{}
dir -Directory | foreach {
$logResult.($_.Name) = $_.Name | Get-ChildItem -Filter '*.dat' |
foreach {
$isMatch = $_.Name -match 'BKP_DS_FETCHER_(?<hour>.*).dat$'
if($isMatch) {
$Matches.hour
}
}
}
$hourProperties = ($logResult.Values | foreach {$_}) | Sort-Object | Get-Unique
$logResult.Keys | foreach {
$obj = New-Object pscustomobject
$obj | Add-Member -MemberType NoteProperty -Name 'FOLDER_NAME' -Value $_
foreach($p in $hourProperties) {
$v=$null
if($logResult[$_] -contains $p) {
$v = 'Success'
}
else {
$v = 'Failed'
}
$obj | Add-Member -MemberType NoteProperty -Name $p -Value $v
}
$obj
} | Format-Table
Pop-Location
}
Pop-Location
Here is a way to do it in PowerShell:
$date = Get-Date -Format 'yyyy-MM-dd'
$basePath = 'D:\data\Backup\'
$outputPath = 'D:\data\Backup\output_' + $date + '.txt'
$baseFileName = 'BKP_DS_FETCHER_[HOUR].dat'
$hours = #( '6AM', '10AM', '2PM', '6PM' )
function Check-Folder( $folderName )
{
$resultLine = New-Object -TypeName System.Object
$resultLine | Add-Member -MemberType NoteProperty -Name 'FOLDER_NAME' -Value $folderName
foreach( $h in $script:hours )
{
$path = $script:basePath + $folderName + '\' + $script:date + '\' + $script:baseFileName.Replace('[HOUR]',$h)
#Write-Host $path
if( Test-Path -Path $path )
{
$resultLine | Add-Member -MemberType NoteProperty -Name $h -Value 'Success'
}
else
{
$resultLine | Add-Member -MemberType NoteProperty -Name $h -Value 'Failed'
}
}
return $resultLine
}
$results = #()
$results += Check-Folder 'WINDOWSDATA'
$results += Check-Folder 'LINUXDATA'
$results += Check-Folder 'UBUNTUDATA'
$results | Format-Table -AutoSize | Out-File -FilePath $outputPath
Output will look like this:
FOLDER_NAME 6AM 10AM 2PM 6PM
----------- --- ---- --- ---
WINDOWSDATA Success Failed Success Success
LINUXDATA Failed Failed Failed Failed
UBUNTUDATA Failed Success Failed Failed

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

Resources