Performing a "WHILE" Loop in an Asnyc Function - powershell

I definitely feel like I've done my due diligence in trying to sort this one out, so bear with me as I try to explain what I'd like to accomplish.
I have a PowerShell script that automates many different offboarding tasks. Some of these tasks can take quite some time. For example, waiting on an e-mail export to complete can take hours depending on the employee and their length of time with the company. So, rather than performing my "while" loop in the script itself (and log jamming all of the next steps) I am trying to pass it off to an async function to run in the background while the rest of my offboarding script moves along.
I can tell that the async function successfully runs and the required variables are being passed, but... nothing is happening. I'm getting no updates printed to my log file. I'm getting no updates to the entry in my SQL db. I'm truly at a loss with this one.
Here is the call to my function in the main script:
$VaultParameters = #{
employee_name = "$employee_name"
export_name = "$export_name"
sql_id = "$sql_id"
vault_status_id = "$vault_status_id"
}
VaultExport #VaultParameters
And here is the function itself:
function VaultExport {
param (
[cmdletbinding()]
[parameter()]
[string]$employee_name,
[parameter()]
[string]$export_name,
[parameter()]
[string]$sql_id,
[parameter()]
[string]$vault_status_id
)
$scriptBlock = {
param ($employee_name,$export_name,$sql_id,$vault_status_id)
$vault_ready = "no"
while ($vault_ready -eq "no") {
$vault_status = gam info export "Email Exports" "$export_name"
$vault_status = $vault_status -Match "COMPLETED"
$vault_status = $vault_status -split(": ")
$vault_status = $vault_status[1]
Invoke-SqlUpdate -Query "UPDATE ``db_name``.``table_name`` SET ``vault_status`` = '$vault_status' WHERE ``id`` = '$vault_status_id'"
if ($vault_status -eq "COMPLETED") {
$vault_ready = "yes"
$completed = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Invoke-SqlUpdate -Query "UPDATE ``db_name``.``table_name`` SET ``vault_completed`` = '$completed' WHERE ``id`` = '$vault_status_id'"
Invoke-SqlUpdate -Query "UPDATE ``db_name``.``table_name`` SET ``vault_status`` = '1' WHERE ``id`` = '$sql_id'"
$LoggingParameters = #{
logfile = "C:\script_logs\threxit.log"
log = "INFO: (GVEF, $employee_name) $export_name is ready ($vault_status). Downloading now..."
}
EventLogging #LoggingParameters
Write-Output "$export_name is ready ($vault_status). Downloading now..."
} else {
$vault_status = gam info export "Email Exports" "$export_name"
$vault_status = $vault_status -Match "IN_PROGRESS"
$vault_status = $vault_status -split(": ")
$vault_status = $vault_status[1]
$LoggingParameters = #{
logfile = "C:\script_logs\threxit.log"
log = "INFO: (GVEF, $employee_name) $export_name is not yet ready ($vault_status). Checking again in ten seconds."
}
EventLogging #LoggingParameters
Write-Output "$export_name is not yet ready ($vault_status). Checking again in ten seconds."
Start-Sleep 10
}
}
}
Start-Job -ScriptBlock $scriptBlock -ArgumentList #($employee_name,$export_name,$sql_id,$vault_status_id)
}
exit

Related

Read-host not passing variable?

So I writing some code to run some patching on AWS, I have the following script snippet taken out of the whole thing for now.. I seem to be running into an issue with $PSBoundParameters..
(It's Friday & I've had a long week so I may just need to sleep on it) - but I can't seem to pass anything out of read-host...
param (
[Parameter(Mandatory = $true)][ValidateNotNullorEmpty()][string]$Prof,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()][string]$Reminder,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()][string]$AddToTGs,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()][string]$PatchType,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()][string]$Instance,
[Parameter(Mandatory = $false)][ValidateNotNullorEmpty()][string]$Environment
)
function PromptInstance {
$Instance = Read-Host -Prompt "Please Specify the Instance"
Write-Host "Using: $Instance" -ForegroundColor Cyan
}
function PromptEnvtoPatch {
$Environment = Read-Host -Prompt "Please Specify the Environment (e.g. dev)"
Write-Host "Using: $Environment" -ForegroundColor Cyan
}
function PromptReminder {
$title = "Calendar"
$message = "Do you want to Add an Outlook Reminder in 24 Hrs?"
$pA = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Adds an Outlook Reminder"
$pB = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Won''t add a reminder"
$options = [System.Management.Automation.Host.ChoiceDescription[]]($pA, $pB)
$CalResult = $host.ui.PromptForChoice($title, $message, $options, 0)
switch ($CalResult)
{
0 {
$global:CalRes = 1
Write-Host "Reminder will be added.." -ForegroundColor Cyan
}
1 {
$global:CalRes = 0
Write-Host "No Reminder will be added.." -ForegroundColor Cyan
}
}
}
function PromptAddToTGs {
$title = "Re-Add to Target Groups"
$message = "Do you want to have this script Automatically add the instances back into the Target Groups after Patching?"
$pA = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Will ADD the instance back into Target Groups"
$pB = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Will NOT add the instance back into Target Groups"
$options = [System.Management.Automation.Host.ChoiceDescription[]]($pA, $pB)
$result = $host.ui.PromptForChoice($title, $message, $options, 1)
switch ($result)
{
0 {
$global:AddTGRes = 1
Write-Host "Instances WILL be added back into Target Groups" -ForegroundColor Cyan
}
1 {
$global:AddTGRes = 0
Write-Host "Instances will NOT be added back into Target Groups" -ForegroundColor Cyan
}
}
}
function PromptPatchType {
$title = "Patching Type"
$message = "Do you want to Patch a Single Instance, or ALL Instances for a specific Team Env?"
$pA = New-Object System.Management.Automation.Host.ChoiceDescription "&Instance", "Patches an Instance"
$pB = New-Object System.Management.Automation.Host.ChoiceDescription "&ALL Instances for an Env", "Patches ALL Instances in a Team Env"
$pQ = New-Object System.Management.Automation.Host.ChoiceDescription "&Quit", "Cancel/Exit"
$options = [System.Management.Automation.Host.ChoiceDescription[]]($pA, $pB, $pQ)
$PatchResult = $host.ui.PromptForChoice($title, $message, $options, 0)
switch ($PatchResult)
{
0 {
$Instance = Read-Host "Please Specify the Instance Id"
}
1 {
$Environment = Read-Host "Please Specify the Team (i.e. dev)"
}
2 {
Write-Host "You Quitter!... :-)"
Exit
}
}
}
function KickOffPatchingEnv {
param ($Prof, $Reg, $Reminder, $AddToTGs, $PatchType, $Environment)
Write-Host "Using the Following Options: (Profile:$Prof) (Region:$Reg) (Reminder:$Reminder) (AddToTGs:$AddToTGs) (PatchType:$PatchType) (Environment:$Environment)"
}
function KickOffPatchingInst {
param ($Prof, $Reg, $Reminder, $AddToTGs, $PatchType, $Instance)
Write-Host "Using the Following Options: (Profile:$Prof) (Region:$Reg) (Reminder:$Reminder) (AddToTGs:$AddToTGs) (PatchType:$PatchType) (Instance:$Instance)"
}
switch -wildcard ($Prof) {
"*dev*" { $Reg = "eu-west-1"; $Bucket = "s3-dev-bucket" }
"*admin*" { $Reg = "eu-west-1"; $Bucket = "s3-admin-bucket" }
"*prod*" { $Reg = "eu-west-1"; $Bucket = "s3-prod-bucket" }
"*staging*" { $Reg = "eu-west-1"; $Bucket = "s3-staging-bucket" }
}
if (!$PSBoundParameters['Reminder']) { PromptReminder }
if (!$PSBoundParameters['AddToTGs']) { PromptAddToTGs }
if ($PSBoundParameters['PatchType']) {
if (($PatchType -eq "i") -or ($PatchType -eq "instance") -or ($PatchType -eq "I") -or ($PatchType -eq "Instance")) {
if (!$PSBoundParameters['Instance']) { PromptInstance }
KickOffPatchingInst $Prof $Reg $Reminder $AddToTGs $PatchType $Instance
}
if (($PatchType -eq "a") -or ($PatchType -eq "all") -or ($PatchType -eq "A") -or ($PatchType -eq "All")) {
if (!$PSBoundParameters['Environment']) { PromptEnvtoPatch }
KickOffPatchingEnv $Prof $Reg $Reminder $AddToTGs $PatchType $Environment
}
} else { PromptPatchType }
If I use the parameters on the command line, it works fine..
PS C:\Users\myself\Desktop> .\test.ps1 -Prof dev -Reminder y -AddToTGs y -PatchType a -Environment dev
Using the Following Options: (Profile:dev) (Region:eu-west-1) (Reminder:y) (AddToTGs:y) (PatchType:a) (Environment:dev)
But if I omit an option, say for instance the Environment, I'm prompted for it, but the value is not displayed..
PS C:\Users\myself\Desktop> .\test.ps1 -Prof dev -Reminder y -AddToTGs y -PatchType a
Please Specify the Environment (e.g. dev): dev
Using: dev
Using the Following Options: (Profile:dev) (Region:eu-west-1) (Reminder:y) (AddToTGs:y) (PatchType:a) (Environment:)
Environment is empty....
I've tried loads of different things such as setting global:Environment etc, but I always seem to be missing out whichever variable isn't specified in the command?
Maybe this isn't the best way to write this, but i've never used $PSBoundParameters before so this is my first time trying it out..
Can anyone see my glaring error here at all?
TIA :)
#Mathias R. Jessen answered this for me.
I put this in the function
function PromptProfile {
$Prof = Read-Host -Prompt "Please Specify the Profile"
$global:UserProf = $Prof
}
then changed code later on with the global variable, so I can use them

PowerShell Script in Task Scheduler with Async Function

I have a fairly large PowerShell script that I've broken into two separate scripts. The first script ends with a call to an async function and then exits.
The function checks on the status of an e-mail export that was generated in the previous script, performing a WHILE loop where it makes a call to Google every sixty seconds to see if the export is ready. Once the export is ready it updates a couple of SQL dbs and my second script knows to take over.
This works 100% of the time when I run the "first script" in the shell/console. But I've started noticing that when my scheduled task is triggered in Task Scheduler that... nothing happens. I have extensive logging, so I know that the parameters ARE being sent over to the async function, but it seems to just poop out rather than continue to loop through the WHILE and do the every-sixty-second checks.
I feel like I've done my due diligence in Googling here, but... is there something I'm missing with a Task Scheduler job to ensure that a function containing a WHILE loop will properly run?
EDIT BELOW
To better explain what I'm doing I will include stripped code from my function and the call to the function from the main script below.
First, the call to the function at the very end of "script_01."
# Let's send the Google Vault export information over to our function.
Try {
$VaultParameters = #{
employee_name = "$employee_name"
export_name = "$export_name"
sql_id = "$sql_id"
vault_status_id = "$vault_status_id"
}
VaultExport #VaultParameters
$LoggingParameters = #{
logfile = "C:\script_logs\log.log"
log = "INFO: Sent the Google Vault export information over to our async function."
}
EventLogging #LoggingParameters
} Catch {
$LoggingParameters = #{
logfile = "C:\script_logs\log.log"
log = "ERROR: Could not send the Google Vault export information over to our async function.`n$_"
}
EventLogging #LoggingParameters
}
And now the function itself. It is large...
function VaultExport {
param (
[cmdletbinding()]
[parameter()]
[string]$employee_name,
[parameter()]
[string]$export_name,
[parameter()]
[string]$sql_id,
[parameter()]
[string]$vault_status_id
)
$scriptBlock = {
param ($employee_name,$export_name,$sql_id,$vault_status_id)
Import-Module SimplySQL
$logfile = "C:\script_logs\log.log"
$now = (Get-Date).tostring("MM-dd-yyyy hh:mm:ss")
# Let's define our MySQL database credentials for later use.
# DEFINING SQL CREDS HERE
# Let's generate secure credentials for our MySQL 'terms' db.
# GENERATING SECURE CREDS HERE
# And now we'll connect to our SQL db...
# CONNECTING TO SQL HERE
$vault_ready = "no"
Add-Content $logfile "$now INFO: Beginning the WHILE loop while $export_name completes..."
while ($vault_ready -eq "no") {
$vault_status = gam info export "Email Exports" "$export_name"
$vault_status = $vault_status -Match "COMPLETED"
$vault_status = $vault_status -split(": ")
$vault_status = $vault_status[1]
if ($vault_status -eq "COMPLETED") {
$vault_ready = "yes"
$completed = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Invoke-SqlUpdate -Query "UPDATE ``table`` SET ``vault_status`` = '$vault_status', ``vault_completed`` = '$completed' WHERE ``id`` = '$vault_status_id'"
Invoke-SqlUpdate -Query "UPDATE ``table`` SET ``vault_status`` = '1' WHERE ``id`` = '$sql_id'"
$now = (Get-Date).tostring("MM-dd-yyyy hh:mm:ss")
Add-Content $logfile "$now INFO: $export_name is ready to download. Updated vault_status in our dbs."
} else {
$vault_status = gam info export "Email Exports" "$export_name"
$vault_status = $vault_status -Match "IN_PROGRESS"
$vault_status = $vault_status -split(": ")
$vault_status = $vault_status[1]
Invoke-SqlUpdate -Query "UPDATE ``table`` SET ``vault_status`` = '$vault_status' WHERE ``id`` = '$vault_status_id'"
$now = (Get-Date).tostring("MM-dd-yyyy hh:mm:ss")
Add-Content $logfile "$now INFO: $export_name is not yet ready: ($vault_status). Checking again in sixty seconds."
Start-Sleep 60
}
}
}
Start-Job -ScriptBlock $scriptBlock -ArgumentList #($employee_name,$export_name,$sql_id,$vault_status_id)
}
exit
I'm unsure why you wouldn't just have the task execute every 30 seconds instead of having a process run indefinitely and using it's own timers
The condition you are setting for the WHILE statement is being met, this is why the loop doesn't continue.
Change the condition to something that will never be met and the problem should go away, i.e.
$Value1 = 0
WHILE($Value1 -ne 1){
#do things, never updating the $value1 variable
}

Powershell Error: Exception Calling "Approve" with "2" argument(s): Value cannot be null

I'm writing a powershell script to automate WSUS. One of the functions approves non-superseded updates to a sandbox testing computer group in order to download/install them on the console. However, all updates it finds return this same error. Here is the code for my definitions and the approval function:
[String]$updateServer1 = hostname
[Boolean]$useSecureConnection = $False
[Int32]$portNumber = 8530
[void][reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")
$updateServer = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($updateServer1,$useSecureConnection,$portNumber)
$updatescope = New-Object Microsoft.UpdateServices.Administration.UpdateScope
$u = $updateServer.GetUpdates($updatescope)
$install = [Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Install
$group = $updateServer.GetComputerTargetGroups | where-object {$_.Name -eq "Update Testing"}
function Approve-Nonsuperseded {
Write-host "Creating new Computer Group to approve updates for installation..." -foregroundcolor green
try {
$updateserver.CreateComputerTargetGroup("Update Testing")
}
catch {
Write-host "Update Group already exists. Moving on..." -ForegroundColor Green
}
$count = 0
Write-host "Approving new updates for installation..." -foregroundcolor green
foreach ($u2 in $u )
{
if ($u2.IsDeclined -ne 'True' -and $u2.IsSuperseded -ne 'True' -and $u2.CreationDate -ge $PatchDay)
{
write-host Approving Update : $u2.Title
$u2.Approve($install,$group)
$count = $count + 1
}
}
write-host Total Approved Updates: $count
}
It returns all the correct updates that are meant to be approved, but always gives me that same error on the $u2.Approve($install,$group) line. I'd appreciate any insight. Thanks!
$u2 should be a string and not an variable. Try using $u2.Approve("Install",$group)
The [Microsoft.UpdateServices.Administration.UpdateApprovalAction] namespace only provides the options ;)

How to run a module within a Scriptblock in PowerShell?

I am currently trying to import a .psm1 file dynamically into a script block to execute it.
I am using parallelisation along with jobs as I need to trigger several modules simultaneously as different users.
This is the code:
$tasksToRun | ForEach-Object -Parallel {
$ScriptBlock = {
param ($scriptName, $Logger, $GlobalConfig, $scriptsRootFolder )
Write-Output ("hello $($scriptsRootFolder)\tasks\$($scriptName)")
Import-Module ("$($scriptsRootFolder)\tasks\$($scriptName)")
& $scriptName -Logger $Logger -GlobalConfig $GlobalConfig
}
$job = Start-Job -scriptblock $ScriptBlock `
-credential $Cred -Name $_ `
-ArgumentList ($_, $using:Logger, $using:globalConfig, $using:scriptsRootFolder) `
Write-Host ("Running task $_")
$job | Wait-job -Timeout $using:timeout
if ($job.State -eq 'Running') {
# Job is still running, stop it
$job.StopJob()
Write-Host "Stopped $($job.Name) task as it took too long"
}
else {
# Job completed normally, get the results
$job | Receive-Job
Write-Host "Finished task $($job.Name)"
}
}
The logger variable is a hashtable as defined here:
$Logger = #{
generalLog = $function:Logger
certificateLog = $function:LoggerCertificate
alertLog = $function:LoggerAlert
endpointServiceLog = $function:LoggerEndpointService
}
Currently, it is erroring with the following:
ObjectNotFound: The term
' blah blah blah, this is the code straight from the logger function '
is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
The logger function servers the purpose of logging to a file in a specific way, it is generalised to that it can be used across many tasks.
A cut down example of a logger (probably won't compile, just deleted a bunch of lines to give you the general idea):
function LoggerEndpointService {
param (
# The full service name.
[string]$ServiceFullName,
# The unique identifier of the service assigned by the operating system.
[string]$ServiceId,
# The description of the service.
[string]$Description,
# The friendly service name.
[string]$ServiceFriendlyName,
# The start mode for the service. (disabled, manual, auto)
[string]$StartMode,
# The status of the service. (critical, started, stopped, warning)
[string]$Status,
# The user account associated with the service.
[string]$User,
# The vendor and product name of the Endpoint solution that reported the event, such as Carbon Black Cb Response.
[string]$VendorProduct
)
$ServiceFullName = If ([string]::IsNullOrEmpty($ServiceFullName)) { "" } Else { $ServiceFullName }
$ServiceId = If ([string]::IsNullOrEmpty($ServiceId)) { "" } Else { $ServiceId }
$ServiceFriendlyName = If ([string]::IsNullOrEmpty($ServiceFriendlyName)) { "" } Else { $ServServiceFriendlyNameiceName }
$StartMode = If ([string]::IsNullOrEmpty($StartMode)) { "" } Else { $StartMode }
$Status = If ([string]::IsNullOrEmpty($Status)) { "" } Else { $Status }
$User = If ([string]::IsNullOrEmpty($User)) { "" } Else { $User }
$Description = If ([string]::IsNullOrEmpty($Description)) { "" } Else { $Description }
$VendorProduct = If ([string]::IsNullOrEmpty($VendorProduct)) { "" } Else { $VendorProduct }
$EventTimeStamp = Get-Date -Format "yyyy-MM-ddTHH:mm:ssK"
$Delay = 100
For ($i = 0; $i -lt 30; $i++) {
try {
$logLine = "{{timestamp=""{0}"" dest=""{1}"" description=""{2}"" service=""{3}"" service_id=""{4}""" `
+ "service_name=""{5}"" start_mode=""{6}"" vendor_product=""{7}"" user=""{8}"" status=""{9}""}}"
$logLine -f $EventTimeStamp, $env:ComputerName, $Description, $ServiceFullName, $ServiceId, $ServiceFriendlyName, $StartMode, $VendorProduct, $User, $Status | Add-Content $LogFile -ErrorAction Stop
break;
}
catch {
Start-Sleep -Milliseconds $Delay
}
if ($i -eq 29) {
Write-Error "Alert logger failed to log, likely due to Splunk holding the file, check eventlog for details." -ErrorAction Continue
if ([System.Diagnostics.EventLog]::SourceExists("SDOLiveScripts") -eq $False) {
Write-Host "Doesn't exist"
New-EventLog -LogName Application -Source "SDOLiveScripts"
}
Write-EventLog -LogName "Application" -Source "SDOLiveScripts" `
-EventID 1337 `
-EntryType Error `
-Message "Failed to log to file $_.Exception.InnerException.Message" `
-ErrorAction Continue
}
}
}
Export-ModuleMember -Function LoggerEndpointService
If anyone could help that'd be great, thank you!
As mentioned in the comments, PowerShell Jobs execute in separate processes and you can't share live objects across process boundaries.
By the time the job executes, $Logger.generalLog is no longer a reference to the scriptblock registered as the Logger function in the calling process - it's just a string, containing the definition of the source function.
You can re-create it from the source code:
$actualLogger = [scriptblock]::Create($Logger.generalLog)
or, in your case, to recreate all of them:
#($Logger.Keys) |ForEach-Object { $Logger[$_] = [scriptblock]::Create($Logger[$_]) }
This will only work if the logging functions are completely independent of their environment - any references to variables in the calling scope or belonging to the source module will fail to resolve!

Powershell script for checking AD replication

I adapted an AD replication powershell script I found online to include the code below:
function ExitWithCode {
param
(
$exitcode
)
$host.SetShouldExit($exitcode)
exit
}
function Write-Log {
<#
.SYNOPSIS
Write-Log writes a message to a logfile
.DESCRIPTION
The Write-Log function is designed to add logging capability to other scripts.
In addition to writing output and/or verbose you can write to a log file for
later debugging.
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[Alias('LogContent')]
[string]$Message,
[Parameter(Mandatory = $false)]
[ValidateSet("Error", "Info", "Status")]
[string]$Level = "Info",
[Parameter(Mandatory = $false)]
[Alias('LogPath')]
[string]$Path = 'C:\dataloop\ADHealthCheck.log'
)
BEGIN {
[string]$FormattedDate = Get-Date -Format "dd-MM-yyyy HH:mm"
If (-NOT (Test-Path $path)) {
Write-Verbose "Creating $Path"
[System.IO.FileInfo]$LogFile = New-Item $Path -Force -ItemType file
}
}
PROCESS {
[string]$LogLine = "$FormattedDate - $Level - $message"
$LogLine | Out-File -FilePath $Path -Append
Switch ($Level) {
"Info" {Write-Verbose $LogLine}
"Status" {Write-Output $LogLine}
"Error" {Write-Error $LogLine}
}
}
END {}
}
function Get-ADHealthCheck {
[CmdletBinding()]
param()
BEGIN {
Write-Log "Beginning the AD Health Check..."
}
PROCESS {
$DCs = Get-ADDomainController -Filter * |sort name
Write-Log "$($DCs.Count) Domain Controllers found" -level Info
$results = #()
ForEach ($DC in $DCs) {
Write-Log "Getting replication metadata for $($DC.HostName)" -level Status
$ReplStatuses = Get-ADReplicationPartnerMetadata -target $DC.HostName -PartnerType Both -ErrorAction SilentlyContinue
If ($ReplStatuses) {
Write-Log "$($ReplStatuses.Count) replication links found for $($DC.HostName)" -level Info
ForEach ($ReplStatus in $ReplStatuses) {
$Partner = $ReplStatus.Partner.Split(",")[1].Replace("CN=","")
$results += [pscustomobject] #{
'Source DC' = $DC.HostName.ToUpper()
'Partner DC' = (Get-ADComputer $Partner).DNSHostName.ToUpper()
'Direction' = $ReplStatus.PartnerType
'Type' = $ReplStatus.IntersiteTransportType
'Last Attempt' = $ReplStatus.LastReplicationAttempt
'Last Success' = $ReplStatus.LastReplicationSuccess
'Last Result' = $ReplStatus.LastReplicationResult
}
}
}
Else {
Write-Log "Unable to get replication status for $($DC.HostName)" -level Error
$results += [pscustomobject] #{
'Source DC' = $DC.HostName.ToUpper()
'Partner DC' = "N/A"
Direction = "N/A"
Type = "N/A"
'Last Attempt' = "N/A"
'Last Success' = "N/A"
'Last Result' = "N/A"
}
}
}
ForEach ($result in $results) {
If ("$($results.'Last Result')" -eq "0") {
Write-Log "There were no replication issues found" -Level Info
ExitWithCode -exitcode 0
}
Else {
Write-Log "These domain controllers have replication errors. Please review them..." -Level Error
$error = $results | where {"$($_.'Last Result')" -ne "0"} | select 'Source DC','Partner DC','Direction' | ft -AutoSize
Write-Log $error -Level Error
ExitWithCode -exitcode 2
}
}
}
}
Get-ADHealthCheck
Basically the only issue I'm having now is the last if/else block. I need it to loop through every entry in the $results hash table and if the "Last Result" key only contains "0", then exit with code 0. If it finds any other values, it should output the source, partner, and direction value(s) fromt he hash table.
Currently, if it encounters an issue, it jumps to the else block, outputs the information requested and then runs the ExitWithCode function which eventually kills the script so anything that comes after the error is not checked.
I've been looking at this too long and have been unsuccessful so I'm throwing it out to there since it may just be something simple I'm missing.
Look at your for loop variables
ForEach ($result in $results) {
For each single $result in the $results. In the following if statement you should be looking at one $result but instead you are doing a comparison against all results. Your subexpression syntax here is also not required.
If ("$($results.'Last Result')" -eq "0")
Note that this is perfectly valid code but it will not get you the results you expect. It will return all 'last result's that are 0. So if even one in the whole collection is 0 the true condition will always fire.
So lets just make some minor changes and use the singular $result
If ($result.'Last Result' -eq "0")
That should get you the result you are looking for. I notice that you are looking for equality with the string 0. That will not be an issue here since the LHS sets the type for the comparison. Have a look at this other post to get a better understanding of what PowerShell does here.
Why is $false -eq "" true?
If your last result was the int 0 this would be true
0 -eq "0"
Your logic appears to be flawed as you have mentioned in comments on how to handle overall success and individual failures. Instead of looping through the results like you were I think we need to check the collection as a whole and loop only if errors are encountered.
# Lets check if any of the results contain failure
$failedChecks = $results | Where-object{$_.'Last Result' -ne 0}
# Evaluate $failedChecks as a boolean. If there are no failed checks Else can be assumed that everything is fine.
if($failedChecks){
Write-Log "These domain controllers have replication errors. Please review them..." -Level Error
$error = $failedChecks | select 'Source DC','Partner DC','Direction' | ft -AutoSize | Out-String
Write-Log $error -Level Error
ExitWithCode -exitcode 2
} else {
Write-Log "There were no replication issues found" -Level Info
ExitWithCode -exitcode 0
}