PowerShell: Working with the error from Standard Out (using Nessus Essentials) - powershell

Trying to use PowerShell to capture the running status of the "Nessus Essentials" software product. Simply trying to capture product status: running, not running, or other. Getting the below error each time. I've tried changing -like to -match and changing string [warn] [scanner] Not linked to a manager to various other shorter versions, with wildcards and without, to no avail. I still get several lines of an ugly error message when all I want is one line with the string Not linked to a manager returned to console with nothing beneath that.
Pertinent snippet working incorrectly:
} elseif(($agentStatus.stdOut -like "[warn] [scanner] Not linked to a manager")) {
Throw "Not linked to a manager"
The Error:
The Code:
Function Start-ProcessGetStreams {
[CmdLetBinding()]
Param(
[System.IO.FileInfo]$FilePath,
[string[]]$ArgumentList
)
$pInfo = New-Object System.Diagnostics.ProcessStartInfo
$pInfo.FileName = $FilePath
$pInfo.Arguments = $ArgumentList
$pInfo.RedirectStandardError = $true
$pInfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pInfo.CreateNoWindow = $true
$pInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
$proc = New-Object System.Diagnostics.Process
$proc.StartInfo = $pInfo
Write-Verbose "Starting $FilePath"
$proc.Start() | Out-Null
Write-Verbose "Waiting for $($FilePath.BaseName) to complete"
$proc.WaitForExit()
$stdOut = $proc.StandardOutput.ReadToEnd()
$stdErr = $proc.StandardError.ReadToEnd()
$exitCode = $proc.ExitCode
Write-Verbose "Standard Output: $stdOut"
Write-Verbose "Standard Error: $stdErr"
Write-Verbose "Exit Code: $exitCode"
[PSCustomObject]#{
"StdOut" = $stdOut
"Stderr" = $stdErr
"ExitCode" = $exitCode
}
}
Function Get-NessusStatsFromStdOut {
Param(
[string]$stdOut
)
$stats = New-Object System.Collections.Hashtable
$StdOut -split "`r`n" | % {
if($_ -like "*:*") {
$result = $_ -split ":"
$stats.add(($result[0].Trim() -replace "[^A-Za-z0-9]","_").ToLower(),$result[1].Trim())
}
}
Return $stats
}
Function Get-DateFromEpochSeconds {
Param(
[int]$seconds
)
$utcTime = (Get-Date 01.01.1970)+([System.TimeSpan]::fromseconds($seconds))
Return Get-Date $utcTime.ToLocalTime() -Format "yyyy-MM-dd HH:mm:ss"
}
Try {
$nessusExe = Join-Path $env:ProgramFiles -ChildPath "Tenable\Nessus\nessuscli.exe" -ErrorAction Stop
} Catch {
Throw "Cannot find NessusCli.exe"
}
Write-Host "Getting Agent Status..."
$agentStatus = Start-ProcessGetStreams -FilePath $nessusExe -ArgumentList "managed status"
If($agentStatus.stdOut -eq "" -and $agentStatus.StdErr -eq "") {
Throw "No Data Returned from NessusCli"
} elseif($agentStatus.StdOut -eq "" -and $agentStatus.StdErr -ne "") {
Throw "StdErr: $($agentStatus.StdErr)"
} elseif(($agentStatus.stdOut -like "[warn] [scanner] Not linked to a manager")) {
Throw "Not linked to a manager"
} elseif(-not($agentStatus.stdOut -like "*Running: *")) {
Throw "StdOut: $($agentStatus.StdOut)"
} else {
$stats = Get-NessusStatsFromStdOut -stdOut $agentStatus.StdOut
If($stats.last_connection_attempt -as [int]) { $stats.last_connection_attempt = Get-DateFromEpochSeconds $stats.last_connection_attempt }
If($stats.last_connect -as [int]) { $stats.last_connect = Get-DateFromEpochSeconds $stats.last_connect }
If($stats.last_scanned -as [int]) { $stats.last_connect = Get-DateFromEpochSeconds $stats.last_scanned }
}
$stats | Out-Host
Note: Code above is courtesy of here, I've only made a change to the path of Nessus, and I am adding the attempt to capture that it's not connected to a manager.

Modify your code so that it separates standard output from error and so that it handles each line separately.
The following is how to capture standard output (excluding else statements and error handling) of a program (according to your $Proc variable)
if ($proc.Start())
{
while (!$proc.StandardOutput.EndOfStream)
{
$StreamLine = $proc.StandardOutput.ReadLine()
if (![string]::IsNullOrEmpty($StreamLine))
{
# TODO: Duplicate code in this scope as needed or rewrite to use multiline regex
$WantedLine = [regex]::Match($StreamLine, "(?<wanted>.*Not linked to a manager.*)")
$Capture = $WantedLine.Groups["wanted"]
if ($Capture.Success)
{
Write-Output $Capture.Value
}
}
}
}
After that deal with error output separately:
$StandardError = $Proc.StandardError.ReadToEnd()
if (![string]::IsNullOrEmpty($StandardError))
{
# Or use Write-Error
Write-Output $StandardError
}

Related

Only prints the last server in the list, I want all servers

This only prints the last server in the list, I'm looking to get all servers and print to screen
$machines = (Get-BrokerMachine -AdminAddress $adminaddress -DesktopGroupName $deliverygroup | Select-Object DNSname).DNSname
foreach($machine in $machines){
$machinelist = Get-BrokerMachine -HostedMachineName $machine
if($machinelist.InMaintenanceMode -eq $true){
$status = "$machine is in maintenance mode"
}else {
$status = "$machine is not in maintenance mode"
}
}
Write-Host $status
Here is a more PowerShell-like approach (not tested):
Get-BrokerMachine -AdminAddress $adminaddress -DesktopGroupName $deliverygroup | ForEach-Object {
$machineName = $_.DNSName
[PSCustomObject] #{
"MachineName" = $machineName
"MaintenanceMode" = (Get-BrokerMachine -HostedMachineName $machine).InMaintenanceMode
}
} | Export-Csv "C:\whatever\results.csv" -NoTypeInformation
$Status is constantly being overwritten by the current machine in your list.
You're looking for:
$Status+=
As opposed to:
$Status=
You'll also want to explicitly state that $Status will be an array at the beginning like so:
$Status=#()
Or when you create the variable and omit the line at the beginning.
[array]$Status +=
Otherwise, you'll get results that run together as it will be treated as a [String]
another funky mode :
function get-BrokerMachineMode
{
param (
[Parameter(Mandatory = $true)]
[string[]]$machines
)
begin
{
$ErrorActionPreference = 'Stop'
Add-Type -Language CSharp #"
public class BrokenBroker {
qpublic System.String MachineName;
public System.String MaintenanceMode;
public BrokenBroker (string MachineName, string MaintenanceMode)
{
this.MachineName = MachineName;
this.MaintenanceMode = IsInMaintenanceMode;
}
}
"#
$status = #()
Write-Verbose "Created objects..."
}
process
{
try
{
$machines = (Get-BrokerMachine -AdminAddress $adminaddress `
-DesktopGroupName $deliverygroup | Select-Object DNSname).DNSname
foreach ($machine in $machines)
{
Write-Verbose "Checking machine: $machine"
$machinelist = Get-BrokerMachine -HostedMachineName $machine
if ($machinelist.InMaintenanceMode -eq $true)
{
$status += New-Object BrokenBroker($machine, $true)
}
else
{
$status += New-Object BrokenBroker($machine, $false)
}
}
}
catch
{
Write-Error $error[0].Exception.Message
}
$status
}
end
{
Write-Verbose "Done"
}
}
this is a function you just must to load then you can launch it just by using this command:
$computers = get-content = {PATH TO TXT FILE}
$list = get-BrokerMachineMode -machines $computers -Verbose

Unable to gather error info from halting Powershell script

I'm writing a script that will watch a directory for any new mp4 files, then convert the file using HandBrake's CLI tool. The logic that watches the directory for changes works by itself, but if I drop a large video into the "watched" directory the conversion fails since it kicks off as soon as it sees a new file, before the file has time to finish copying.
I'm using a do until loop to check if the file is locked/downloading and then continues once the file is unlocked/writable. The loop works as a stand-alone script, but when used inside the filesystem watcher the script it will halt without any errors on this line:
[System.IO.FileStream] $fs = $convertingFile.OpenWrite();
This occurs regardless if $ErrorActionPreference = "SilentlyContinue" is commented out. I've been unable to gather any log output to see why the script is halting using Start-Transcript or Out-File.
How should I best gather error info about why the script halts once it hits this line?
Bonus: Why might this script not provide error information?
$ErrorActionPreference = "SilentlyContinue"
function Start-FileSystemWatcher {
[CmdletBinding()]
param(
[Parameter()]
[string]$Path,
[Parameter()]
[ValidateSet('Changed','Created','Deleted','Renamed')]
[string[]]$EventName,
[Parameter()]
[string]$Filter,
[Parameter()]
[System.IO.NotifyFilters]$NotifyFilter,
[Parameter()]
[switch]$Recurse,
[Parameter()]
[scriptblock]$Action
)
#region Build FileSystemWatcher
$FileSystemWatcher = New-Object System.IO.FileSystemWatcher
if (-not $PSBoundParameters.ContainsKey('Path')) {
$Path = $PWD
}
$FileSystemWatcher.Path = $Path
if ($PSBoundParameters.ContainsKey('Filter')) {
$FileSystemWatcher.Filter = $Filter
}
if ($PSBoundParameters.ContainsKey('NotifyFilter')) {
$FileSystemWatcher.NotifyFilter = $NotifyFilter
}
if ($PSBoundParameters.ContainsKey('Recurse')) {
$FileSystemWatcher.IncludeSubdirectories = $True
}
if (-not $PSBoundParameters.ContainsKey('EventName')) {
$EventName = 'Changed','Created','Deleted','Renamed'
}
if (-not $PSBoundParameters.ContainsKey('Action')) {
$Action = {
switch ($Event.SourceEventArgs.ChangeType) {
'Renamed' {
$Object = "{0} was {1} to {2} at {3}" -f $Event.SourceArgs[-1].OldFullPath,
$Event.SourceEventArgs.ChangeType,
$Event.SourceArgs[-1].FullPath,
$Event.TimeGenerated
}
Default {
$Object = "{0} was {1} at {2}" -f $Event.SourceEventArgs.FullPath,
$Event.SourceEventArgs.ChangeType,
$Event.TimeGenerated
}
}
$WriteHostParams = #{
ForegroundColor = 'Green'
BackgroundColor = 'Black'
Object = $Object
}
Write-Host #WriteHostParams
}
}
$ObjectEventParams = #{
InputObject = $FileSystemWatcher
Action = $Action
}
foreach ($Item in $EventName) {
$ObjectEventParams.EventName = $Item
$ObjectEventParams.SourceIdentifier = "File.$($Item)"
Write-Verbose "Starting watcher for Event: $($Item)"
$Null = Register-ObjectEvent #ObjectEventParams
}
}
$FileSystemWatcherParams = #{
Path = 'X:\share\scripts\ps\converter\input'
Recurse = $True
NotifyFilter = 'FileName'
Verbose = $True
Action = {
$Item = Get-Item $Event.SourceEventArgs.FullPath
$WriteHostParams = #{
ForegroundColor = 'Green'
BackgroundColor = 'Black'
}
$inputFile = "${PWD}\input\$($Item.Name)".trim()
$outputFile = "${PWD}\output\$($Item.Name)".trim()
$logFile = "${PWD}\log\$($Item.Name).txt"
$testLogFile = "${PWD}\log\$($Item.Name)(t).txt"
function mp4-Func {
Start-Transcript -path $logFile
Write-Host "New mp4 file detected..."
$convertingFile = New-Object -TypeName System.IO.FileInfo -ArgumentList $inputFile
$locked = 1
do {
[System.IO.FileStream] $fs = $convertingFile.OpenWrite();
if (!$?) {
Write-Host "Can't convert yet, file appears to be loading..."
sleep 2
}
else {
$fs.Dispose()
$locked = 0
}
} until ($locked -eq 0)
Write-Host "File unlocked and ready for conversion."
HandBrake
Stop-Transcript
$WriteHostParams.Object = "Finished converting: $($Item.Name)"
}
function HandBrake {
.\HandBrakeCLI.exe --input "$inputFile" `
--output "$outputFile" `
--format av_mp4 `
--encoder x264 `
--vb 1700 `
--two-pass `
--aencoder copy:aac `
--ab 320 `
--arate 48 `
--mixdown stereo
}
switch -regex ($Item.Extension) {
'\.mp4' { mp4-Func }
}
Write-Host #WriteHostParams
}
}
#( 'Created') | ForEach-Object {
$FileSystemWatcherParams.EventName = $_
Start-FileSystemWatcher #FileSystemWatcherParams
}
I think you'll find that $ErrorActionPreference only impact errors at the level of cmdlets, while you hitting a problem in non-cmdlet code. For that, you'll probably need a try/catch construct.
Based on Burt_Harris' answer (please upvote his answer over this one), I changed the "do while" loop so that it would use try/catch rather than an if/else statement. By using $.Exception.Message and $.Exception.ItemName I was able to better understand why the script was halting at that particular line.
Working code:
Do {
Try {
[System.IO.FileStream] $fs = $convertingFile.OpenWrite()
$fs.Dispose()
$locked = 0
}
Catch {
$ErrorMessage = $_.Exception.Message
$FailedItem = $_.Exception.ItemName
Write-Host $ErrorMessage
Write-Host $FailedItem
Write-Host "Can't convert yet, file appears to be loading..."
sleep 5
continue
}
} until ($locked -eq 0)

How to loop through a script to let a user selection different options?

Fairly new to Powershell. I'm working on a script that will let a user install different pieces of software that will be needed. This process is currently being done manually and it can take 30-45 minutes to install everything that is needed. However, not everything needs to be installed on each workstation so I need flexibility for the user. This is what I have come up with so far. (Modified for brevity)
$Software1Path = "Path to installer"
$Software2Path = "Path to installer"
$Software3Path = "Path to installer"
function Software1 {
$Software1Arguments = "/S"
Start-Process -FilePath $Software1Installer $Software1Arguments -Wait
InstallOptions
}
function Software2 {
$Software2Arguments = "/silent"
Start-Process -FilePath $Software2Installer $Software2Arguments -Wait
InstallOptions
}
function Software3 {
$Software3Arguments = "/passive"
Start-Process -FilePath $Software3Installer $Software3Arguments -Wait
InstallOptions
}
function InstallOptions {
Do {
Clear-Host
Write-Host('1. Install Software1')
Write-Host('2. Install Software1')
Write-Host('3. Install Software1')
Write-Host('4. Install All Three')
Write-Host('0. Exit')
$value = Read-Host 'Input your selection (0-3)'
}
Until ($value -eq "o"){
switch ($value) {
"0" {exit}
"1" { Software 1}
"2" { Software 2}
"3" { Software 3}
"4" { Software1
Software2
Software3}
}
}
}
It does not give the desired result. I can either install one piece of software but then the script exits and I also cannot install all three. I've played with the InstallOptions and wrote it ten different ways but I am still not getting the desired result. Any suggestions?
You can call InstallOptions in a while($true) {} (as long as you keep 0 to exit) or another type of loop to make it return to the menu. If you call the menu from the software installation-function it will never get to 2 and 3 when you use "install all". Try:
$Software1Path = "Path to installer"
$Software2Path = "Path to installer"
$Software3Path = "Path to installer"
function Software1 {
$Software1Arguments = "/S"
Start-Process -FilePath $Software1Path -ArgumentList $Software1Arguments -Wait
}
function Software2 {
$Software2Arguments = "/silent"
Start-Process -FilePath $Software2Path -ArgumentList $Software2Arguments -Wait
}
function Software3 {
$Software3Arguments = "/passive"
Start-Process -FilePath $Software3Path -ArgumentList $Software3Arguments -Wait
}
function InstallOptions {
Clear-Host
Write-Host('1. Install Software1')
Write-Host('2. Install Software2')
Write-Host('3. Install Software3')
Write-Host('4. Install All Three')
Write-Host('0. Exit')
$value = Read-Host 'Input your selection (0-3)'
switch ($value) {
"0" {exit}
"1" { Software1}
"2" { Software2}
"3" { Software3}
"4" {
Software1
Software2
Software3
}
}
}
#Start the train
while($true) { InstallOptions }
You might also want to clean this up. Ex. $Sofware1Path is outside the function while the $Software1Arguments are inside. For simple installations, you could clean this up to use ex. a csv-stored array (can be read stored in a separate file if needed). Something like:
$Installations = #(#"
Name,Step,Path,Arguments
Software1,1,"c:\Install Files\Product1\Setup.exe",/S
Software2,2,"c:\Install Files\Product2\SetupPart2.exe",/silent
Software2,1,"c:\Install Files\Product2\Setup.exe","/silent ""space test with ,"""
Software3,1,"c:\Install Files\Product3\Setup.exe",/passive "space test"
"# | ConvertFrom-Csv)
function InstallSoftware ($Name) {
Write-Host "Installing $Name..."
$Installations | Where-Object { $_.Name -eq $Name } | Sort-Object { $_.Step -as [int] } | ForEach-Object {
ExecuteStep -Path $_.Path -Arguments $_.Arguments
}
}
function ExecuteStep ($Path, $Arguments) {
Write-Host "Executing '$Path' '$Arguments'"
Start-Process -FilePath $Path -ArgumentList $Arguments -Wait
}
function Menu {
$UniqueSoftware = $Installations | Select-Object -ExpandProperty Name -Unique | Sort-Object
$NumOfSoftware = $UniqueSoftware.Count
#Generate menu
Clear-Host
for($i=0;$i -lt $NumOfSoftware; $i++) {
Write-Host ("{0}. Install {1}" -f ($i+1), $Software[$i].Name)
}
Write-Host ("{0}. Install all" -f ($NumOfSoftware+1))
Write-Host "0. Exit"
do {
#Get input
$value = (Read-Host "Input your selection (0-$($NumOfSoftware+1))") -as [int]
#Execute
switch ($value) {
0 { exit }
{ $_ -gt 0 -and $_ -le $NumOfSoftware } { InstallSoftware -Name $UniqueSoftware[($_-1)] }
($NumOfSoftware+1) { 0..($NumOfSoftware-1) | ForEach-Object { InstallSoftware -Name $UniqueSoftware[($_)] } }
default { Write-Host "Invalid input..." }
}
#Validate input or retry
} until ( $value -ge 0 -and $value -le $NumOfSoftware+1 )
}
#Start the train
while($true) { Menu }

Powershell script to get certificate expiry for a website remotely for multiple servers

I am trying to create an script to get the certificate expiry date for an websites remotely for multiple servers. I have an script which is working for single server (Need to login into server and doing execution), I need to run this remotely for multiple servers. How can i modify this script to execute for multiple servers remotely. Please advice.
$servers = get-content D:\Certificate.txt
$DaysToExpiration = 60 #change this once it's working
$expirationDate = (Get-Date).AddDays($DaysToExpiration)
foreach ($server in $servers)
{
$sites = Get-Website | ? { $_.State -eq "Started" } | % { $_.Name }
$certs = Get-ChildItem IIS:SSLBindings | ? {
$sites -contains $_.Sites.Value
} | % { $_.Thumbprint }
Get-ChildItem CERT:LocalMachine/My | ? {
$certs -contains $_.Thumbprint -and $_.NotAfter -lt $expirationDate
}
}
Inspired by https://iamoffthebus.wordpress.com/2014/02/04/powershell-to-get-remote-websites-ssl-certificate-expiration/ I use following script:
$minimumCertAgeDays = 60
$timeoutMilliseconds = 10000
$urls = get-content .\check-urls.txt
#disabling the cert validation check. This is what makes this whole thing work with invalid certs...
[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
foreach ($url in $urls)
{
Write-Host Checking $url -f Green
$req = [Net.HttpWebRequest]::Create($url)
$req.Timeout = $timeoutMilliseconds
$req.AllowAutoRedirect = $false
try {$req.GetResponse() |Out-Null} catch {Write-Host Exception while checking URL $url`: $_ -f Red}
$certExpiresOnString = $req.ServicePoint.Certificate.GetExpirationDateString()
#Write-Host "Certificate expires on (string): $certExpiresOnString"
[datetime]$expiration = [System.DateTime]::Parse($req.ServicePoint.Certificate.GetExpirationDateString())
#Write-Host "Certificate expires on (datetime): $expiration"
[int]$certExpiresIn = ($expiration - $(get-date)).Days
$certName = $req.ServicePoint.Certificate.GetName()
$certPublicKeyString = $req.ServicePoint.Certificate.GetPublicKeyString()
$certSerialNumber = $req.ServicePoint.Certificate.GetSerialNumberString()
$certThumbprint = $req.ServicePoint.Certificate.GetCertHashString()
$certEffectiveDate = $req.ServicePoint.Certificate.GetEffectiveDateString()
$certIssuer = $req.ServicePoint.Certificate.GetIssuerName()
if ($certExpiresIn -gt $minimumCertAgeDays)
{
Write-Host Cert for site $url expires in $certExpiresIn days [on $expiration] -f Green
}
else
{
Write-Host WARNING: Cert for site $url expires in $certExpiresIn days [on $expiration] -f Red
Write-Host Threshold is $minimumCertAgeDays days. Check details:`nCert name: $certName -f Red
Write-Host Cert public key: $certPublicKeyString -f Red
Write-Host Cert serial number: $certSerialNumber`nCert thumbprint: $certThumbprint`nCert effective date: $certEffectiveDate`nCert issuer: $certIssuer -f Red
}
Write-Host
rv req
rv expiration
rv certExpiresIn
}
Alternatively, you might find this advanced script useful:
you can switch between report output as Text, Html or PSObject
use the script with urls (parameter array) or with input file for urls or with pipeline input
improved stability: correctly handle missing certificates on HTTP connections
just put the code into a file like Check-ExpiringSslCerts.ps1
Here the advanced script code:
[CmdletBinding(DefaultParametersetname="URLs in text file")]
Param(
[ValidateSet('Text','Html','PSObject')]
[string]$ReportType = 'Text',
[int]$MinimumCertAgeDays = 60,
[int]$TimeoutMilliseconds = 10000,
[parameter(Mandatory=$false,ParameterSetName = "URLs in text file")]
[string]$UrlsFile = '.\check-urls.txt',
[parameter(Mandatory=$false,ParameterSetName = "List of URLs",
ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string[]]$Urls
)
Begin
{
[string[]]$allUrls = #()
$returnData = #()
[bool]$ProcessedInputPipeLineByArrayItem = $false
function CheckUrl ([string]$url, [array]$returnData)
{
[string]$details = $null
if ($ReportType -eq "Html")
{
$stringHtmlEncoded = [System.Web.HttpUtility]::HtmlEncode($url)
Write-Host "<tr><td>$stringHtmlEncoded</td>"
}
if ($ReportType -eq "Text") { Write-Host Checking $url }
$req = [Net.HttpWebRequest]::Create($url)
$req.Timeout = $timeoutMilliseconds
$req.AllowAutoRedirect = $false
try
{
$req.GetResponse() |Out-Null
if ($req.ServicePoint.Certificate -eq $null) {$details = "No certificate in use for connection"}
}
catch
{
$details = "Exception while checking URL $url`: $_ "
}
if ($details -eq $null -or $details -eq "")
{
$certExpiresOnString = $req.ServicePoint.Certificate.GetExpirationDateString()
#Write-Host "Certificate expires on (string): $certExpiresOnString"
[datetime]$expiration = [System.DateTime]::Parse($req.ServicePoint.Certificate.GetExpirationDateString())
#Write-Host "Certificate expires on (datetime): $expiration"
[int]$certExpiresIn = ($expiration - $(get-date)).Days
$certName = $req.ServicePoint.Certificate.GetName()
$certPublicKeyString = $req.ServicePoint.Certificate.GetPublicKeyString()
$certSerialNumber = $req.ServicePoint.Certificate.GetSerialNumberString()
$certThumbprint = $req.ServicePoint.Certificate.GetCertHashString()
$certEffectiveDate = $req.ServicePoint.Certificate.GetEffectiveDateString()
$certIssuer = $req.ServicePoint.Certificate.GetIssuerName()
if ($certExpiresIn -gt $minimumCertAgeDays)
{
if ($ReportType -eq "Html")
{
Write-Host "<td>OKAY</td><td>$certExpiresIn</td><td>$expiration</td><td> </td></tr>"
}
if ($ReportType -eq "Text")
{
Write-Host OKAY: Cert for site $url expires in $certExpiresIn days [on $expiration] -f Green
}
if ($ReportType -eq "PSObject")
{
$returnData += new-object psobject -property #{Url = $url; CheckResult = "OKAY"; CertExpiresInDays = [int]$certExpiresIn; ExpirationOn = [datetime]$expiration; Details = [string]$null}
}
}
else
{
$details = ""
$details += "Cert for site $url expires in $certExpiresIn days [on $expiration]`n"
$details += "Threshold is $minimumCertAgeDays days. Check details:`n"
$details += "Cert name: $certName`n"
$details += "Cert public key: $certPublicKeyString`n"
$details += "Cert serial number: $certSerialNumber`n"
$details += "Cert thumbprint: $certThumbprint`n"
$details += "Cert effective date: $certEffectiveDate`n"
$details += "Cert issuer: $certIssuer"
if ($ReportType -eq "Html")
{
Write-Host "<td>WARNING</td><td>$certExpiresIn</td><td>$expiration</td>"
$stringHtmlEncoded = [System.Web.HttpUtility]::HtmlEncode($details) -replace "`n", "<br />"
Write-Host "<tr><td>$stringHtmlEncoded</td></tr>"
}
if ($ReportType -eq "Text")
{
Write-Host WARNING: $details -f Red
}
if ($ReportType -eq "PSObject")
{
$returnData += new-object psobject -property #{Url = $url; CheckResult = "WARNING"; CertExpiresInDays = [int]$certExpiresIn; ExpirationOn = [datetime]$expiration; Details = $details}
}
rv expiration
rv certExpiresIn
}
}
else
{
if ($ReportType -eq "Html")
{
Write-Host "<td>ERROR</td><td>N/A</td><td>N/A</td>"
$stringHtmlEncoded = [System.Web.HttpUtility]::HtmlEncode($details) -replace "`n", "<br />"
Write-Host "<tr><td>$stringHtmlEncoded</td></tr>"
}
if ($ReportType -eq "Text")
{
Write-Host ERROR: $details -f Red
}
if ($ReportType -eq "PSObject")
{
$returnData += new-object psobject -property #{Url = $url; CheckResult = "ERROR"; CertExpiresInDays = $null; ExpirationOn = $null; Details = $details}
}
}
if ($ReportType -eq "Text") { Write-Host }
rv req
return $returnData
}
#disabling the cert validation check. This is what makes this whole thing work with invalid certs...
[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
if ($ReportType -eq "Html")
{
Write-Host "<table><tr><th>URL</th><th>Check result</th><th>Expires in days</th><th>Expires on</th><th>Details</th></tr>"
Add-Type -AssemblyName System.Web
}
}
Process
{
if ($_ -ne $null)
{
CheckUrl $_ $returnData
$ProcessedInputPipeLineByArrayItem = $true
}
}
End
{
if ($ProcessedInputPipeLineByArrayItem -eq $false)
{
if ($Urls -eq $null)
{
$allUrls = get-content $UrlsFile
}
else
{
$allUrls = $Urls
}
foreach ($url in $allUrls)
{
$returnData = CheckUrl $url $returnData
}
}
if ($ReportType -eq "Html") { Write-Host "</table>" }
if ($ReportType -eq "PSObject") { return $returnData }
}
Output might look like e.g.:
"http://www.doma.com", "https://www.domb.com" | .\Check-ExpiringSslCerts.ps1 -ReportType PSObject | ft
Url ExpirationOn CertExpiresInDays CheckResult Details
--- ------------ ----------------- ----------- -------
http://www.doma.com ERROR No certificate in use for connection
https://www.domb.com 18.11.2017 09:33:00 87 OKAY
Put the whole code you've wrote in a script-block, in order to do so, just add at the beginning this code:
$sb = {
and at the bottom of your code add:
}
Once you have this script-block, you can run this script on the servers remotely using these commands:
$cred = Get-Credential
$servers = get-content D:\Certificate.txt
Invoke-Command -Credential $cred -ComputerName $servers -ScriptBlock $SB
Hope it helped!
Your code snippets are helpful but they will throw an error on HTTP errors -- that is the nature of the underlying .NET HttpWebRequest object to panic on everything that's not code 200.
So, if you're testing, say, API endpoints that only accept POST HTTP verb, your script will fail as it will get a 405 error message. Alternatively, if your URL returns HTTP 404, your script will fail as well. Luckily, you can catch layer 7 errors and capture the required info anyway just patch your code in this manner:
try {$req.GetResponse() | Out-Null } catch {
if ($_.Exception.InnerException.Status -eq 'ProtocolError') {
# saving the info anyway since this is a L7 error, e.g.:
$certExpiresOnString = $req.ServicePoint.Certificate.GetExpirationDateString()
[datetime]$expiration [System.DateTime]::Parse($req.ServicePoint.Certificate.GetExpirationDateString())
$certIssuer = $req.ServicePoint.Certificate.GetIssuerName() # ...
} else {
# this is a real error - timeout, DNS failure etc
Write-Host "$url, $_" -ForegroundColor Red
Continue
}
}
Taken mostly from https://gist.github.com/jstangroome/5945820, although with some changes. The following will obtain the certificate. $certificate.NotBefore and $certificate.NotAfter will then need to be checked.
function GetCertificate([string]$domain, [Int16]$port) {
$certificate = $null
$TcpClient = New-Object -TypeName System.Net.Sockets.TcpClient
$TcpClient.ReceiveTimeout = 1000
$TcpClient.SendTimeout = 1000
try {
$TcpClient.Connect($domain, $port)
$TcpStream = $TcpClient.GetStream()
$Callback = { param($sender, $cert, $chain, $errors) return $true }
$SslStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList #($TcpStream, $true, $Callback)
try {
$SslStream.AuthenticateAsClient($domain)
$certificate = $SslStream.RemoteCertificate
}
finally {
$SslStream.Dispose()
}
}
finally {
$TcpClient.Dispose()
}
if ($certificate) {
if ($certificate -isnot [System.Security.Cryptography.X509Certificates.X509Certificate2]) {
$certificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $certificate
}
}
return $certificate
}

Application runs in Windows 7 but not in Windows 10

I have created an app for back up and restore of computers. I also allows modification of ADObjects through the use of custom Profile.ps1 file. The app runs fine in the ISE with no errors and works properly no errors in Windows 7. However, when I try to run it in a newly imaged Windows 10 machine I get "Property Can Not Be Found" errors on all my object properties.
The thing is I can read all the properties when I fill comboboxes fine. The error only occurs when the form is submitted. I will attach 1 of the forms I am having a problem with. Again it runs fine in Windows 7, but not Windows 10.
Could this be a problem with Microsoft updates?
Also, yes, I am setting Set-ExecutionPolicy -Scope Process -ExecutionPolicy Unrestricted.
Error message:
The property 'company' cannot be found on this object. Verify that the
property exist and can be set.
+ $CO.company = $company
+ Categoryinfo :InvalidOperation: (:) [] RuntimeExeption
Code:
. \\iotsdsp01pw\installs$\MoveToOU\PcDeployment\Profile.ps1
#region Validation Functions
function Validate-IsEmail ([string]$Email) {
return $Email -match "^(?("")("".+?""#)|(([0-9a-zA-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-zA-Z])#))(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,6}))$"
}
function Validate-IsURL ([string]$Url) {
if ($Url -eq $null) {
return $false
}
return $Url -match "^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_]*)?$"
}
function Validate-IsName ([string]$Name, [int]$MaxLength) {
if ($MaxLength -eq $null -or $MaxLength -le 0) {
#Set default length to 40
$MaxLength = 40
}
return $Name -match "^[a-zA-Z''-'\s]{1,$MaxLength}$"
}
function Validate-IsIP ([string]$IP) {
return $IP -match "\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b"
}
function Validate-IsEmptyTrim ([string]$Text) {
if ($text -eq $null -or $text.Trim().Length -eq 0) {
return $true
}
return $false
}
function Validate-IsEmpty ([string]$Text) {
return [string]::IsNullOrEmpty($Text)
}
function Validate-IsDate ([string]$Date) {
return [DateTime]::TryParse($Date, [ref](New-Object System.DateTime))
}
#endregion
$No_Load = {
$NewForm.Close()
#Initialize variables
$dateTime = Get-Date -Format G
$userName = (Get-WmiObject -Class Win32_ComputerSystem -Property UserName).UserName
$computerName = $env:computername
#Varables for display
$distinguishedName = (Get-dn computer cn $computerName)
$computerObject = (Get-ADObject $distinguishedName)
$organizationalUnit = (Get-ADObject "OU=Agencies, DC=state, DC=in, DC=us")
#Initialize Form Controls
$lblUserNameNewNo.Text = $userName
$lblComputerNameNewNo.Text = $computerName
$lblPhysicalLocationNewNo.Text = $computerObject.location
$txtBillingCodeNewNo.Text = $computerObject.departmentNumber
$comboboxAccountTypeNewNo.Text = $computerObject.extensionAttribute15
$comboboxOrganizationalUnitNewNo.Text = $computerObject.company
Load-ComboBox -ComboBox $comboboxOrganizationalUnitNewNo ($organizationalUnit.children | %{ $_.OU })
}
#region Control Helper Functions
function Load-ComboBox {
Param (
[ValidateNotNull()]
[Parameter(Mandatory = $true)]
[System.Windows.Forms.ComboBox]$ComboBox,
[ValidateNotNull()]
[Parameter(Mandatory = $true)]
$Items,
[Parameter(Mandatory = $false)]
[string]$DisplayMember,
[switch]$Append
)
if (-not $Append) {
$ComboBox.Items.Clear()
}
if ($Items -is [Object[]]) {
$ComboBox.Items.AddRange($Items)
} elseif ($Items -is [Array]) {
$ComboBox.BeginUpdate()
foreach ($obj in $Items) {
$ComboBox.Items.Add($obj)
}
$ComboBox.EndUpdate()
} else {
$ComboBox.Items.Add($Items)
}
$ComboBox.DisplayMember = $DisplayMember
}
#Validation
function ParameterValidate {
Param (
[Parameter(Mandatory = $true)]
[ValidateNotNull()]
[ValidateLength(1, 10)]
[String]$Text
)
return $true
}
$comboboxOrganizationalUnitNewNo_Validating = [System.ComponentModel.CancelEventHandler]{
#Check if the Name field is empty
$result = Validate-IsEmptyTrim $comboboxOrganizationalUnitNewNo.Text
if ($result -eq $true) {
#Mark a failure only if the Validation failed
$script:ValidationFailed = $true
#Display an error message
$errorprovider1.SetError($comboboxOrganizationalUnitNewNo, "Please select agency.");
} else {
#Clear the error message
$errorprovider1.SetError($comboboxOrganizationalUnitNewNo, "");
}
}
$txtBillingCodeNewNo_Validating = [System.ComponentModel.CancelEventHandler]{
#Check if the Name field is empty
$result = Validate-IsEmptyTrim $txtBillingCodeNewNo.Text
if ($result -eq $true) {
#Mark a failure only if the Validation failed
$script:ValidationFailed = $true
#Display an error message
$errorprovider1.SetError($txtBillingCodeNewNo, "Please enter billing code.");
} else {
#Clear the error message
$errorprovider1.SetError($txtBillingCodeNewNo, "");
}
}
$comboboxAccountTypeNewNo_Validating = [System.ComponentModel.CancelEventHandler]{
$result = Validate-IsEmptyTrim $comboboxAccountTypeNewNo.Text
if ($result -eq $true) {
#Mark a failure only if the Validation failed
$script:ValidationFailed = $true
#Display an error message
$errorprovider1.SetError($comboboxAccountTypeNewNo, "Please enter agency type.");
} else {
#Clear the error message
$errorprovider1.SetError($comboboxAccountTypeNewNo, "");
}
}
$control_Validated = {
#Pass the calling control and clear error message
$errorprovider1.SetError($this, "");
}
$No_FormClosing = [System.Windows.Forms.FormClosingEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.FormClosingEventArgs]
#Validate only on OK Button
if ($No.DialogResult -eq "OK") {
#Init the Validation Failed Variable
$script:ValidationFailed = $false
#Validate the Child Control and Cancel if any fail
$No.ValidateChildren()
#Cancel if Validation Failed
$_.Cancel = $script:ValidationFailed
}
}
#Events
$buttonColor_Click = {
#TODO: Place custom script here
$colordialog1.ShowDialog()
}
$linklblViewListNewNo_LinkClicked = [System.Windows.Forms.LinkLabelLinkClickedEventHandler]{
Start-Process "http://billingcodes.iot/"
}
$btnSubmitNewNo_Click = {
#TODO: Place custom script here
$company = $comboboxOrganizationalUnitNewNo.Text
$departmentNumber = $txtBillingCodeNewNo.Text
$accountType = $comboboxAccountTypeNewNo.Text
if ($accountType -eq "Seat") {
$accountType = " "
}
#Varables for Set-ADObject
$computerObject.company = $company
$computerObject.departmentNumber = $departmentNumber
$computerObject.extensionAttribute15 = $accountType
try {
$computerObject.SetInfo()
[Environment]::Exit(1)
} catch {
$labelDialogRedNewNo.Text = "AD computer object not found"
}
}
This is your culprit (from what I can see):
$No_Load = {
...
$computerObject = (Get-ADObject $distinguishedName)
...
}
...
$btnSubmitNewNo_Click = {
...
$computerObject.company = $company
...
}
You assign a computer object to the variable $computerObject in one scriptblock, and try to change one of its properties in another scriptblock. However, to be able to share a variable between scriptblocks you need to make it a global variable, otherwise you have two separate local variables that know nothing about each other.
$No_Load = {
...
$global:computerObject = Get-ADObject $distinguishedName
...
}
...
$btnSubmitNewNo_Click = {
...
$global:computerObject.company = $company
...
}
BTW, I doubt that this ever worked in Windows 7, since it failed with the same error on my Windows 7 test box.