Trouble with retrieving certificate information in Powershell? - powershell

I'm trying to build a dashboard to retrieve certificate information on all our servers, but I'm struggling with the powershell object handling. I believe it's the way objects are getting passed inside and outside a loop. I have 3 iterations of my code.
In the first, all certificates are retrieved, but the FriendlyName is blanked out on every object:
$serverCert = $null
$servers=get-adcomputer -filter { ( OperatingSystem -like '*server*') -AND ( Name -notlike '*-DT0094' ) } | sort Name
foreach ( $server in $servers ) {
$ServerName=$server.Name
$ServerName="$ServerName.DOMAINSUFFIX"
$serverCert += Invoke-Command -ComputerName $ServerName -Scriptblock {
return $(Get-ChildItem Cert:\LocalMachine\My)
}
}
$serverCert | Select-Object PSComputerName, Thumbprint, FriendlyName, NotAfter, #{N="Template";E={($_.Extensions | ?{$_.oid.Friendlyname -match "Certificate Template Information"}).Format(0) -replace "(.+)?=(.+)\((.+)?", '$2'}}, #{N="IssuedBy";E={($_.IssuerName.Name -split ',*..=')[1]}}, #{N="Subject";E={($_.Subject -split ',*..=')[1]}} | Sort Thumbprint | Format-Table -Wrap
In this iteration, the Extensions come through like this:
PS C:\WINDOWS> $serverCert[0] | Select-Object -Property Extensions
Extensions
----------
{System.Security.Cryptography.Oid, System.Security.Cryptography.Oid}
In the second, I solved this by explicitly passing the FriendlyName through as a new property called "Description"...unfortunately, now the Template doesn't display:
$serverCert = $null
$servers=get-adcomputer -filter { ( OperatingSystem -like '*server*') -AND ( Name -notlike '*-DT0094' ) } | sort Name
foreach ( $server in $servers ) {
$ServerName=$server.Name
$ServerName="$ServerName.DOMAINSUFFIX"
$serverCert += Invoke-Command -ComputerName $ServerName -Scriptblock {
return $(Get-ChildItem Cert:\LocalMachine\My | Select-Object *, #{N="Description";E={$_.FriendlyName}})
}
}
$serverCert | Select-Object PSComputerName, Thumbprint, Description, NotAfter, #{N="Template";E={($_.Extensions | ?{$_.oid.Friendlyname -match "Certificate Template Information"}).Format(0) -replace "(.+)?=(.+)\((.+)?", '$2'}}, #{N="IssuedBy";E={($_.IssuerName.Name -split ',*..=')[1]}}, #{N="Subject";E={($_.Subject -split ',*..=')[1]}} | Sort Thumbprint | Format-Table -Wrap
In this iteration, the Extensions come through like this, and I can't get template name to display:
PS C:\WINDOWS> $serverCert[0] | Select-Object -Property Extensions
Extensions
----------
{System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension, System.Security.Cryptography.X509Certificates.X509KeyUsageExtension}
Now the third. This time I try to pass the template information forward as a property like the "Description." Problem is, the template information doesn't come through clearly; rather than the friendly name, other info is displayed.
$serverCert = $null
$servers=get-adcomputer -filter { ( OperatingSystem -like '*server*') -AND ( Name -notlike '*-DT0094' ) } | sort Name
foreach ( $server in $servers ) {
$ServerName=$server.Name
$ServerName="$ServerName.DOMAINSUFFIX"
$serverCert += Invoke-Command -ComputerName $ServerName -Scriptblock {
return $(Get-ChildItem Cert:\LocalMachine\My | Select-Object *, #{N="Description";E={$_.FriendlyName}}, #{N="Template";E={($_.Extensions | ?{$_.oid.Friendlyname -match "Certificate Template Information"}).Format(0) -replace "(.+)?=(.+)\((.+)?", '$2'}})
}
}
$serverCert | Select-Object PSComputerName, Thumbprint, Description, NotAfter, Template, #{N="IssuedBy";E={($_.IssuerName.Name -split ',*..=')[1]}}, #{N="Subject";E={($_.Subject -split ',*..=')[1]}} | Sort Thumbprint | Format-Table -Wrap
Template information for some certificates (I can't correlate on template, OS version...anything) looks like this:
Template=1.3.6.1.4.1.311.21.8.16245382.12313948.10571683.3565079.1665071.100.15924968.15384388, Major Version Number=100, Minor Version Number=4
I'm stumped. I am still getting comfortable in powershell, but I don't understand object manipulation well enough to know how to fix this. Any help is appreciated!

I think FriendlyName not getting returned in your first iteration has to do with the way PowerShell is deserializing data when the -ComputerName parameter is used.
In your first iteration, try changing this line:
return $(Get-ChildItem Cert:\LocalMachine\My)
to:
return $(Get-ChildItem Cert:\LocalMachine\My | Select-Object *)
To illustrate the issue, run these three commands, which you would expect to all include the same object properties. Note that FriendlyName is only included in the output of the first two commands:
Invoke-Command -ScriptBlock { gci Cert:\LocalMachine\My } | Select-Object FriendlyName
Invoke-Command -ScriptBlock { gci Cert:\LocalMachine\My | Select-Object * } -ComputerName . | Select-Object FriendlyName
Invoke-Command -ScriptBlock { gci Cert:\LocalMachine\My } -ComputerName . | Select-Object FriendlyName
Edit: This is how I might do it:
Function Get-Cert-Info($ComputerName) {
Invoke-Command -Computer $ComputerName -ScriptBlock {
$certs = Get-ChildItem cert:\localmachine\my
foreach($cert in $certs) {
[pscustomobject]#{
ComputerName = $env:COMPUTERNAME
Thumbprint = $cert.Thumbprint
Description = $cert.FriendlyName
TemplateName = $(
$Template = $cert.Extensions | Where-Object { $_.oid.FriendlyName -match "Certificate Template Information" }
if($Template) {
($Template.Format(0) -split "\(")[0] -replace "Template=", ""
}
)
SAN=$(
try {
$cert.Extensions | Where-Object {$_.Oid.FriendlyName -eq "subject alternative name"} | ForEach {
$SANString = "{0}" -f $_.Format(0)
$SANS = $SANString -split ','
foreach($SAN in $SANS) {
($SAN -split "=")[1]
}
}
} catch {
"n/a"
}
)
IssuedBy=$(($cert.IssuerName.Name -split ',')[0] -replace 'CN=', '')
Subject=$(($cert.Subject -split ',')[0] -replace 'CN=', '')
NotAfter=$cert.NotAfter
}
}
}
}
$servers=get-adcomputer -filter { ( OperatingSystem -like '*server*') -AND ( Name -notlike '*-DT0094' ) } | sort Name
foreach ( $server in $servers ) {
Get-Cert-Info -ComputerName $server.Name
}

I found a way to get the template name though, but lookup would fail, so I did something akin to a replace when I got the ID:
# Get all certificates from all servers
clear-host
$serverCert = $null
ipconfig /flushdns
$servers=get-adcomputer -filter { OperatingSystem -like '*server*' } | sort Name
foreach ( $server in $servers ) {
$ServerName=$server.Name
$ServerName="$ServerName.DOMAINSUFFIX"
$serverCert += Invoke-Command -ComputerName $ServerName -Scriptblock {
return $(Get-ChildItem Cert:\LocalMachine\My | Select-Object *, #{N="Description";E={$_.FriendlyName}}, #{N="TemplateName";E={($_.Extensions | ?{$_.oid.Friendlyname -match "Certificate Template Information"}).Format(0) -replace "(.+)?=(.+)\((.+)?", '$2' -replace 'Template=', '' -replace '1.3.6.1.4.1.311.21.8.16245382.12313948.10571683.3565079.1665071.100.15924968.15384388.*', 'SCCM Client Certificate' -replace '1.3.6.1.4.1.311.21.8.16245382.12313948.10571683.3565079.1665071.100.9941395.14900143.*','IIS Web Servers' -replace '1.3.6.1.4.1.311.21.8.16245382.12313948.10571683.3565079.1665071.100.1979823.4984146.*','WSUS Web Server Certificate'}})
}
}
$serverCert | Select-Object PSComputerName, Thumbprint, Description, NotAfter, TemplateName, #{N="IssuedBy";E={($_.IssuerName.Name -split ',*..=')[1]}}, #{N="Subject";E={($_.Subject -split ',*..=')[1]}} | Sort NotAfter, PSComputerName | Format-Table -Wrap

Related

Output running services to csv with computer name

I need to generate a csv containing running services to csv with the corresponding computer name
I know there is a simple way to do this and I have been tinkering with creating a new psobject, but I am not sure how to pipe the results to the new-object...
Here is what I am using:
$Input = "SomePath"
$Output = "SomeOtherPath"
$CompNames = Get-Content -Path "$Input"
ForEach ($CompName in $CompNames){
Get-Service -ComputerName $CompName | Where-Object {$_.Status -eq "Running"} | Export-csv -Path "$Output"
}
What I need in the CSV is:
ComputerName, ServiceName, DisplayName
basically, I need to add the computer name to the array.
If you want to be able to pipe the results, use a foreach-object.
$Output = "SomeOtherPath"
Get-Content -Path "SomePath" | ForEach-Object {
Get-Service -ComputerName $_ | Where-Object {$_.Status -eq "Running"} | Select-Object ComputerName, ServiceName, DisplayName
} | Export-csv -Path "$Output"
If you want to stick to a foreach statement, collect it all first then export it.
$Output = "SomeOtherPath"
$CompNames = Get-Content -Path "SomePath"
$results = ForEach ($CompName in $CompNames){
Get-Service -ComputerName $CompName | Where-Object {$_.Status -eq "Running"} | Select-Object ComputerName, ServiceName, DisplayName
}
$results | Export-csv -Path "$Output"
Try like this (Don't use $Input as variable name)
$InputX = "SomePath"
$Output = "SomeOtherPath"
$CompNames = Get-Content -Path "$Input"
ForEach ($CompName in $CompNames){
Get-Service -ComputerName $CompName | Where-Object {$_.Status -eq "Running"} | Select-Object ComputerName, ServiceName, DisplayName | Export-csv -Path "$Output"
}

Reboot check is showing all reboots not just last

I have a reboot check script that is run post MW, I need it to pull just the last reboot to verify the servers have been rebooted, currently they pull all reboot history. Below is my script:
$DHCP = (Get-Content -Path "\\termserv\d$\SERVER1\SERVER2\Scripts\morescripts\DHCPServers.txt")
foreach ($Server in $DHCP) {
Get-Service -ComputerName $Server -DisplayName "DHCP Server" |
ConvertTo-Html -Title "PScomputername" -Body "<H3> SERVER2 Uptime Report </H3> " -Property PSComputername >> \\termserv\d$\SERVER1\SERVER2\Server3\SERVER2.html
Get-Service -ComputerName $Server -DisplayName "DHCP Server" |
ConvertTo-Html -Property MachineName,Status,ServiceName |
foreach {
if ($_ -like "*<td>Running</td>*") {
$_ -replace "<tr>", "<tr bgcolor=green>"
} elseif ($_ -like "*<td>Stopped</td>*") {
$_ -replace "<tr>", "<tr bgcolor=red>"
} else {
$_
}
} >> \\termserv\d$\SERVER1\SERVER2\Server3\SERVER2.html
Get-WmiObject win32_operatingsystem -ComputerName $Server |
Select PSComputername, #{n='BootTime';e={$_.ConvertToDateTime($_.LastBootupTime)}} |
ConvertTo-Html -Property PSComputerName,BootTime >> \\termserv\d$\SERVER1\SERVER2\Server3\SERVER2.html
ConvertTo-Html -Property PSComputerName,Installedon,Description,caption >> \\termserv\d$\SERVER1\SERVER2\Server3\SERVER2.html
}
$Print = (Get-Content -Path "\\termserv\d$\SERVER1\SERVER2\Scripts\morescripts\PrintServers.txt")
foreach ($Server in $Print) {
Get-Service -ComputerName $Server -DisplayName "Print Spooler" |
ConvertTo-Html -Property MachineName,Status,ServiceName |
foreach {
if ($_ -like "*<td>Running</td>*") {
$_ -replace "<tr>", "<tr bgcolor=green>"
} elseif ($_ -like "*<td>Stopped</td>*") {
$_ -replace "<tr>", "<tr bgcolor=red>"
} else {
$_
}
} >> \\termserv\d$\SERVER1\SERVER2\Server3\SERVER2.html
Get-WmiObject win32_operatingsystem -ComputerName $Server |
Select PSComputername, #{n='BootTime';e={$_.ConvertToDateTime($_.LastBootupTime)}} |
ConvertTo-Html -Property PSComputerName,BootTime >> \\termserv\d$\SERVER1\SERVER2\Server3\SERVER2.html
ConvertTo-Html -Property PSComputerName,Installedon,Description,caption >> \\termserv\d$\SERVER1\SERVER2\Server3\SERVER2.html
}
You just need to Sort-Object with a calculated property and then tell Select-Object to pick the first item:
Get-WmiObject -ClassName Win32_OperatingSystem -ComputerName $Server |
Sort-Object -Property #{e={$_.ConvertToDateTime($_.LastBootupTime)}} -Descending |
Select-Object -First 1 -Property [...] |
ConvertTo-Html [...]
It should be noted that the final ConvertTo-Html call in each main foreach loop doesn't have anything to convert as far as I can tell. They're going to create an empty HTML document with an empty table and append that to your file.
Also, you're appending multiple HTML documents to the server2.html file. If they're going to be in the same document, you should start the entire file with <html><head><title /></head><body>, then use the -Fragment parameter on all of your ConvertTo-Html calls, and finally close the document with </body></html>. Your current method may work, but you're generating an explicitly invalid HTML document. A browser that renders correctly should only display the first table.

Avoiding or replacing multiple Where-Object statements

I have a script containing a function Create-RebootData containing child functions such as Full and Full has a child function named Generate-RebootData where the output variable $Global:result is created.
Within Full there are multiple Where-Object two statements to filter the $Global:result into by date and time. Example below.
Is there an easier method to accomplish this instead of the multiple Where-Object statements?
The desired result are
Set-StrictMode -Version 1.0
Function Create-RebootData{
[CmdletBinding(SupportsShouldProcess=$true,DefaultParameterSetName="ViewOnly")]
Param(
[Parameter(ParameterSetName="ViewOnly")]
[Switch]$ViewOnly,
[Parameter(ParameterSetName="Full")]
[Switch]$Full,
)
Switch ($PSCmdlet.ParameterSetName){
"ViewOnly"
{
ViewOnly
}
"Full"
{
Full
}
}#end switch
Function Full{
Generate-RebootData
$Global:result | Where-Object {$_ -like '*fri?2:00*' -and $_.MaintenanceWindow `
-notmatch 'all.da.servers' -and $_.Server -match "^ITD"} | % {"{0}" -f $_.Server} | `
Out-File D:\Scripts\Full-Servers.txt -Append
$Global:result | Where-Object {$_ -like '*fri?2:00*' -and $_.MaintenanceWindow `
-notmatch 'all.da.servers' -and $_.Server -match "^ITD"} | `
% {"{0}" -f $_.MaintenanceWindow -replace `
"^NA.+", "$((get-date).AddDays(1).ToString('MM-dd-yy')) 01:50"} | `
Out-File D:\Scripts\Full-Times.txt -Append
}
Function Generate-RebootData{
IF(Get-Command Get-SCOMAlert -ErrorAction SilentlyContinue){}ELSE{Import-Module OperationsManager}
"Get Pend reboot servers from prod"
New-SCOMManagementGroupConnection -ComputerName Server01
$AlertData = Get-SCOMAlert -Criteria "MyString" | Select NetbiosComputerName
New-SCOMManagementGroupConnection -ComputerName Server02
$AlertData += Get-SCOMAlert -Criteria "MyString" | Select NetbiosComputerName
"Remove duplicates"
$AlertDataNoDupe = $AlertData | Sort NetbiosComputerName -Unique
"Create hash table"
$table = #{}
"Populate hash table"
$MaintenanceWindow = Import-Csv D:\Scripts\MaintenanceWindow2.csv
$MaintenanceWindow | ForEach-Object {$table[$_.Computername] = $_.'Collection Name'}
"Create final object"
$Global:result = #{}
"Begin Loop"
$Global:result = $AlertDataNoDupe | ForEach-Object { [PSCustomObject] #{
Server=$_.NetbiosComputerName
MaintenanceWindow= if($table.ContainsKey($_.NetbiosComputerName)){
$table[$_.NetbiosComputerName]
}Else { "Not Found!"}
PingCheck=IF(Test-Connection -Count 1 $_.NetbiosComputerName -Quiet -ErrorAction SilentlyContinue){"Alive"}
ELSE{"Dead"}
LastReboot=Try{$operatingSystem = Get-WmiObject Win32_OperatingSystem -ComputerName $_.NetbiosComputerName -ErrorAction Stop
[Management.ManagementDateTimeConverter]::ToDateTime($operatingSystem.LastBootUpTime)}
Catch{"Access Denied!"}
} }
}
You can do it like this:
$Global:result | Where-Object {
$_ -like '*fri?2:00*' -and
$_.MaintenanceWindow -notmatch 'all.da.servers' -and
$_.Server -match '^ITD'
} | ForEach-Object {
'{0}' -f $_.Server | Out-File D:\Scripts\Full-Servers.txt -Append
'{0}' -f $_.MaintenanceWindow -replace '^NA.+',
'{0} 01:50' -f (Get-Date).AddDays(1).ToString('MM-dd-yy') | Out-File D:\Scripts\Full-Times.txt -Append
}
But I agree with Mathias' comment, this function probably should be refactored.

Powershell looping through message queues

I'm trying to return tables with message queues from three different environments. I could copy and paste the existing code for all three, but I want to make it cleaner and more reusable.
Is there a way to loop through each message queue and return them in separate tables (i.e.: Dev, Dev2, Dev3 queues)?
[object]$dev3Queues = gwmi -class Win32_PerfFormattedData_msmq_MSMQQueue -computerName myServer | Where{$_.Name -like "*dev3*" } | select Name,MessagesInQueue #| Out-File "C:\test.txt"
[object]$dev2Queues = gwmi -class Win32_PerfFormattedData_msmq_MSMQQueue -computerName myServer | Where{$_.Name -like "*dev2*" } | select Name,MessagesInQueue #| Out-File "C:\test2.txt"
[object]$devQueues = gwmi -class Win32_PerfFormattedData_msmq_MSMQQueue -computerName myServer |
Where{$_.Name -notlike "*dev2*" -AND $_.Name -notlike "*dev3*" -AND $_.Name -notlike "*private*" -AND $_.Name -notlike "*Computer Queues*" -AND $_.Name -notlike "*uat*"} | select Name,MessagesInQueue #| Out-File "C:\test3.txt"
$Html = "<html><head>Whoo Queues</head><body><table border=1>"
foreach($element in $devQueues)
{
$Html += "<tr><td>" + $element.Name + "</td><td>"+ $element.MessagesInQueue + "</td> </tr>"
}
$Html += "</table></body></html>"
$Html | out-file C:\temp\DEVQueues.html
#environmentloop - dev,dev2,dev3
#{
#queue loop + html
#}
You can use the ConvertTo-Html cmdlet with the option -Fragment to convert a list of objects to an HTML table of the object properties.
Get-WmiObject -Class Win32_PerfFormattedData_msmq_MSMQQueue |
select Name, MessagesInQueue |
ConvertTo-Html -Fragment
Also, when running Get-WmiObject against a remote server using a WMI filter provides better performance than retrieving all results and filtering them on the local host with Where-Object.
$computer = 'myServer'
$filter = 'Name LIKE "%dev3%"'
Get-WmiObject -Class Win32_PerfFormattedData_msmq_MSMQQueue -Computer $computer `
-Filter $filter
However, since you want to filter the same dataset for various criteria, in your case the best approach might be to first fetch all relevant data from the remote host with a more general WMI filter (to avoid multiple remote connections), and then process them locally with several Where-Object filters:
$server = 'myServer'
$wmiFilter = 'NOT (Name LIKE "%private%" OR Name LIKE "%Computer Queues%" ' +
'OR Name LIKE "%uat%")'
$psFilters = { $_.Name -like "*dev3*" },
{ $_.Name -like "*dev2*" },
{ $_.Name -notlike "*dev2*" -and $_.Name -notlike "*dev3*" }
$data = Get-WmiObject -Class Win32_PerfFormattedData_msmq_MSMQQueue `
-Computer $server -Filter $wmiFilter
'<html><head>Whoo Queues</head><body>'
foreach ($filter in $psFilters) {
$data | ? $filter | select Name, MessagesInQueue | ConvertTo-Html -Fragment
}
'</body></html>'

How to list all the services running with a service account in a server using Powershell

I want to update the password of all the services running under one account on multiple servers using powershell. i tried Get-process, Get-WMIObject cmdlets, but these two commands don't have serviceaccount usage. is there a way to update password of all the services running with an account by passing service account,password as parameters to the script.
To get list of services using a particular account you can do:
Get-WmiObject "win32_service" -Filter "StartName='domain\\user'"
To change the password for these, you can do:
Get-WmiObject "win32_service" -Filter "StartName='domain\\user'" |
%{$_.StopService();$_.Change($null,$null,$null,$null,$null,$null,$null,"blah");}
From here: http://www.send4help.net/change-remote-windows-service-credentials-password-powershel-495
try this:
Function GLOBAL:GET-PROCESSUSER ( $ProcessID ) {
(GET-WMIOBJECT win32_process –filter “Handle=$ProcessID”).GetOwner().User
}
$svcs = Get-Process | Select-Object name, starttime, ID
$a = #()
foreach ($svc in $svcs)
{
if ( $svc.name -ne "Idle" -and $svc.name -ne "System")
{
$al = New-Object System.Object
$al | Add-Member -type NoteProperty -name Name -Value $svc.name
$al | Add-Member -type NoteProperty -name Owner -Value ( get-processuser $svc.id)
$a += $al
}
}
$a
Edit after comment:
$a = (GET-WMIOBJECT win32_service) | ? { $_.startname -eq "domain\\username"} | %{$_.StopService();$_.Change($null,$null,$null,$null,$null,$null,$null,"newpassword");}
This is what you guys need
Get-WMIObject Win32_Service | Where-Object {$_.startname -ne "localSystem" }| Where-Object {$_.startname -ne "NT AUTHORITY\LocalService" } |Where-Object {$_.startname -ne "NT AUTHORITY\NetworkService" } |select startname, name
Yeah - this seems to be the best approach
Get-WMIObject Win32_Service | Where-Object {($_.startname -ne "NT AUTHORITY\LocalService") -and ($_.startname -ne "NT AUTHORITY\NetworkService") -and ($_.startname -ne "localSystem") } `
|select #{ Name = "Service Account " ; Expression = { ( $_.startname ) } }, `
#{ Name = "Service Dispaly Name " ; Expression = { ( $_.name ) } }, StartMode,State, Status | FT -AutoSize