O365AppsMonitoring.psm1

function Remove-EmptyValue { 
    [alias('Remove-EmptyValues')]
    [CmdletBinding()]
    param(
        [alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable,
        [string[]] $ExcludeParameter,
        [switch] $Recursive,
        [int] $Rerun,
        [switch] $DoNotRemoveNull,
        [switch] $DoNotRemoveEmpty,
        [switch] $DoNotRemoveEmptyArray,
        [switch] $DoNotRemoveEmptyDictionary
    )
    foreach ($Key in [string[]] $Hashtable.Keys) {
        if ($Key -notin $ExcludeParameter) {
            if ($Recursive) {
                if ($Hashtable[$Key] -is [System.Collections.IDictionary]) {
                    if ($Hashtable[$Key].Count -eq 0) {
                        if (-not $DoNotRemoveEmptyDictionary) {
                            $Hashtable.Remove($Key)
                        }
                    } else {
                        Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive
                    }
                } else {
                    if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) {
                        $Hashtable.Remove($Key)
                    } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') {
                        $Hashtable.Remove($Key)
                    } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) {
                        $Hashtable.Remove($Key)
                    }
                }
            } else {
                if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) {
                    $Hashtable.Remove($Key)
                } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') {
                    $Hashtable.Remove($Key)
                } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) {
                    $Hashtable.Remove($Key)
                }
            }
        }
    }
    if ($Rerun) {
        for ($i = 0; $i -lt $Rerun; $i++) {
            Remove-EmptyValue -Hashtable $Hashtable -Recursive:$Recursive
        }
    }
}
function Write-Color { 
    <#
    .SYNOPSIS
    Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options.
 
    .DESCRIPTION
    Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options.
 
    It provides:
    - Easy manipulation of colors,
    - Logging output to file (log)
    - Nice formatting options out of the box.
    - Ability to use aliases for parameters
 
    .PARAMETER Text
    Text to display on screen and write to log file if specified.
    Accepts an array of strings.
 
    .PARAMETER Color
    Color of the text. Accepts an array of colors. If more than one color is specified it will loop through colors for each string.
    If there are more strings than colors it will start from the beginning.
    Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
 
    .PARAMETER BackGroundColor
    Color of the background. Accepts an array of colors. If more than one color is specified it will loop through colors for each string.
    If there are more strings than colors it will start from the beginning.
    Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
 
    .PARAMETER StartTab
    Number of tabs to add before text. Default is 0.
 
    .PARAMETER LinesBefore
    Number of empty lines before text. Default is 0.
 
    .PARAMETER LinesAfter
    Number of empty lines after text. Default is 0.
 
    .PARAMETER StartSpaces
    Number of spaces to add before text. Default is 0.
 
    .PARAMETER LogFile
    Path to log file. If not specified no log file will be created.
 
    .PARAMETER DateTimeFormat
    Custom date and time format string. Default is yyyy-MM-dd HH:mm:ss
 
    .PARAMETER LogTime
    If set to $true it will add time to log file. Default is $true.
 
    .PARAMETER LogRetry
    Number of retries to write to log file, in case it can't write to it for some reason, before skipping. Default is 2.
 
    .PARAMETER Encoding
    Encoding of the log file. Default is Unicode.
 
    .PARAMETER ShowTime
    Switch to add time to console output. Default is not set.
 
    .PARAMETER NoNewLine
    Switch to not add new line at the end of the output. Default is not set.
 
    .PARAMETER NoConsoleOutput
    Switch to not output to console. Default all output goes to console.
 
    .EXAMPLE
    Write-Color -Text "Red ", "Green ", "Yellow " -Color Red,Green,Yellow
 
    .EXAMPLE
    Write-Color -Text "This is text in Green ",
                      "followed by red ",
                      "and then we have Magenta... ",
                      "isn't it fun? ",
                      "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan
 
    .EXAMPLE
    Write-Color -Text "This is text in Green ",
                      "followed by red ",
                      "and then we have Magenta... ",
                      "isn't it fun? ",
                      "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan -StartTab 3 -LinesBefore 1 -LinesAfter 1
 
    .EXAMPLE
    Write-Color "1. ", "Option 1" -Color Yellow, Green
    Write-Color "2. ", "Option 2" -Color Yellow, Green
    Write-Color "3. ", "Option 3" -Color Yellow, Green
    Write-Color "4. ", "Option 4" -Color Yellow, Green
    Write-Color "9. ", "Press 9 to exit" -Color Yellow, Gray -LinesBefore 1
 
    .EXAMPLE
    Write-Color -LinesBefore 2 -Text "This little ","message is ", "written to log ", "file as well." `
                -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" -TimeFormat "yyyy-MM-dd HH:mm:ss"
    Write-Color -Text "This can get ","handy if ", "want to display things, and log actions to file ", "at the same time." `
                -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt"
 
    .EXAMPLE
    Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow
    Write-Color -t "my text" -c yellow -b green
    Write-Color -text "my text" -c red
 
    .EXAMPLE
    Write-Color -Text "Testuję czy się ładnie zapisze, czy będą problemy" -Encoding unicode -LogFile 'C:\temp\testinggg.txt' -Color Red -NoConsoleOutput
 
    .NOTES
    Understanding Custom date and time format strings: https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings
    Project support: https://github.com/EvotecIT/PSWriteColor
    Original idea: Josh (https://stackoverflow.com/users/81769/josh)
 
    #>

    [alias('Write-Colour')]
    [CmdletBinding()]
    param (
        [alias ('T')] [String[]]$Text,
        [alias ('C', 'ForegroundColor', 'FGC')] [ConsoleColor[]]$Color = [ConsoleColor]::White,
        [alias ('B', 'BGC')] [ConsoleColor[]]$BackGroundColor = $null,
        [alias ('Indent')][int] $StartTab = 0,
        [int] $LinesBefore = 0,
        [int] $LinesAfter = 0,
        [int] $StartSpaces = 0,
        [alias ('L')] [string] $LogFile = '',
        [Alias('DateFormat', 'TimeFormat')][string] $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss',
        [alias ('LogTimeStamp')][bool] $LogTime = $true,
        [int] $LogRetry = 2,
        [ValidateSet('unknown', 'string', 'unicode', 'bigendianunicode', 'utf8', 'utf7', 'utf32', 'ascii', 'default', 'oem')][string]$Encoding = 'Unicode',
        [switch] $ShowTime,
        [switch] $NoNewLine,
        [alias('HideConsole')][switch] $NoConsoleOutput
    )
    if (-not $NoConsoleOutput) {
        $DefaultColor = $Color[0]
        if ($null -ne $BackGroundColor -and $BackGroundColor.Count -ne $Color.Count) {
            Write-Error "Colors, BackGroundColors parameters count doesn't match. Terminated."
            return
        }
        if ($LinesBefore -ne 0) {
            for ($i = 0; $i -lt $LinesBefore; $i++) {
                Write-Host -Object "`n" -NoNewline 
            } 
        } # Add empty line before
        if ($StartTab -ne 0) {
            for ($i = 0; $i -lt $StartTab; $i++) {
                Write-Host -Object "`t" -NoNewline 
            } 
        }  # Add TABS before text
        if ($StartSpaces -ne 0) {
            for ($i = 0; $i -lt $StartSpaces; $i++) {
                Write-Host -Object ' ' -NoNewline 
            } 
        }  # Add SPACES before text
        if ($ShowTime) {
            Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))] " -NoNewline 
        } # Add Time before output
        if ($Text.Count -ne 0) {
            if ($Color.Count -ge $Text.Count) {
                # the real deal coloring
                if ($null -eq $BackGroundColor) {
                    for ($i = 0; $i -lt $Text.Length; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline 
                    }
                } else {
                    for ($i = 0; $i -lt $Text.Length; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline 
                    }
                }
            } else {
                if ($null -eq $BackGroundColor) {
                    for ($i = 0; $i -lt $Color.Length ; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline 
                    }
                    for ($i = $Color.Length; $i -lt $Text.Length; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -NoNewline 
                    }
                } else {
                    for ($i = 0; $i -lt $Color.Length ; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline 
                    }
                    for ($i = $Color.Length; $i -lt $Text.Length; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -BackgroundColor $BackGroundColor[0] -NoNewline 
                    }
                }
            }
        }
        if ($NoNewLine -eq $true) {
            Write-Host -NoNewline 
        } else {
            Write-Host 
        } # Support for no new line
        if ($LinesAfter -ne 0) {
            for ($i = 0; $i -lt $LinesAfter; $i++) {
                Write-Host -Object "`n" -NoNewline 
            } 
        }  # Add empty line after
    }
    if ($Text.Count -and $LogFile) {
        # Save to file
        $TextToFile = ""
        for ($i = 0; $i -lt $Text.Length; $i++) {
            $TextToFile += $Text[$i]
        }
        $Saved = $false
        $Retry = 0
        Do {
            $Retry++
            try {
                if ($LogTime) {
                    "[$([datetime]::Now.ToString($DateTimeFormat))] $TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false
                } else {
                    "$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false
                }
                $Saved = $true
            } catch {
                if ($Saved -eq $false -and $Retry -eq $LogRetry) {
                    Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Tried ($Retry/$LogRetry))"
                } else {
                    Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Retrying... ($Retry/$LogRetry)"
                }
            }
        } Until ($Saved -eq $true -or $Retry -ge $LogRetry)
    }
}
function Set-AppsMonitoringDefaultSettings {
    [cmdletBinding()]
    param(
        [scriptblock] $Template
    )
    $Variables = [ordered] @{}
    if (-not $Template) {
        $Variables.Template = {
            # Default template is provided, but you can change it to your liking
            EmailText -Text "Hello Service Desk," -LineBreak
            EmailText -Text @(
                "Application ", $App.ApplicationName, " has credentials that are expiring or already expired. "
            ) -Color None, BlueDiamond, None -TextDecoration none, underline, none -FontWeight normal, bold, normal

            EmailList {
                EmailListItem -Text "Application Name: ", $App.ApplicationName -Color None, BlueDiamond, None -FontWeight normal, bold, normal
                EmailListItem -Text "Client ID: ", $App.ClientID -Color None, BlueDiamond, None -FontWeight normal, bold, normal
                EmailListItem -Text "Owner Display Name: ", $App.OwnerDisplayName -Color None, BlueDiamond, None -FontWeight normal, bold, normal
                EmailListItem -Text "Owner Mail: ", $App.OwnerMail -Color None, BlueDiamond, None -FontWeight normal, bold, normal
                EmailListItem -Text "Owner UPN: ", $App.OwnerUserPrincipal -Color None, BlueDiamond, None -FontWeight normal, bold, normal
                EmailListItem -Text "Notes: ", $App.Notes -Color None, BlueDiamond, None -FontWeight normal, bold, normal
                EmailListItem -Text "Total number of credentials: ", $App.KeysCount -Color None, BlueDiamond, None -FontWeight normal, bold, normal
                EmailListItem -Text "Keys types: ", $App.KeysTypes -Color None, BlueDiamond, None -FontWeight normal, bold, normal
            }

            EmailText -Text "Following credentials are available for this application:"

            EmailList {
                foreach ($Credentials in $AppCredentials) {
                    EmailListItem -Text "Key ID: ", $Credentials.KeyId -Color None, BlueDiamond, None -FontWeight normal, bold, normal
                    EmailList {
                        EmailListItem -Text "Key Name: ", $Credentials.KeyDisplayName -Color None, BlueDiamond, None -FontWeight normal, bold, normal
                        EmailListItem -Text "Type: ", $Credentials.Type -Color None, BlueDiamond, None -FontWeight normal, bold, normal
                        EmailListItem -Text "Key Hint: ", $Credentials.Hint -Color None, BlueDiamond, None -FontWeight normal, bold, normal
                        EmailListItem -Text "Days to expire: ", $Credentials.DaysToExpire -Color None, BlueDiamond, None -FontWeight normal, bold, normal
                        EmailListItem -Text "Valid from: ", $Credentials.StartDateTime, " to ", $Credentials.EndDateTime -Color None, BlueDiamond, None, BlueDiamond -FontWeight normal, bold, normal, bold
                        EmailListItem -Text "Is expired: ", $Credentials.Expired -Color None, BlueDiamond, None -FontWeight normal, bold, normal
                    }
                }
            }

            EmailText -Text @(
                "Please update the application credentials as soon as possible, and remove the expired ones. "
            ) -FontWeight normal, normal, bold, normal, normal -Color None, None, Salmon, None, None -LineBreak

            EmailText -Text "Thank you"
        }
    } else {
        $Variables.Template = $Template
    }
    $Variables
}
function Set-LoggingCapabilities {
    [CmdletBinding()]
    param(
        [string] $ApplicationName,
        [string] $LogPath,
        [int] $LogMaximum,
        [switch] $ShowTime,
        [string] $TimeFormat
    )

    $Script:PSDefaultParameterValues = @{
        "Write-Color:LogFile"    = $LogPath
        "Write-Color:ShowTime"   = if ($PSBoundParameters.ContainsKey('ShowTime')) { $ShowTime.IsPresent } else { $null }
        "Write-Color:TimeFormat" = $TimeFormat
    }
    Remove-EmptyValue -Hashtable $Script:PSDefaultParameterValues

    if ($LogPath) {
        $FolderPath = [io.path]::GetDirectoryName($LogPath)
        if (-not (Test-Path -LiteralPath $FolderPath)) {
            $null = New-Item -Path $FolderPath -ItemType Directory -Force -WhatIf:$false
        }
        if ($LogMaximum -gt 0) {
            $CurrentLogs = Get-ChildItem -LiteralPath $FolderPath | Sort-Object -Property CreationTime -Descending | Select-Object -Skip $LogMaximum
            if ($CurrentLogs) {
                Write-Color -Text '[i] ', "Logs directory has more than ", $LogMaximum, " log files. Cleanup required..." -Color Yellow, DarkCyan, Red, DarkCyan
                foreach ($Log in $CurrentLogs) {
                    try {
                        Remove-Item -LiteralPath $Log.FullName -Confirm:$false -WhatIf:$false
                        Write-Color -Text '[+] ', "Deleted ", "$($Log.FullName)" -Color Yellow, White, Green
                    } catch {
                        Write-Color -Text '[-] ', "Couldn't delete log file $($Log.FullName). Error: ', "$($_.Exception.Message) -Color Yellow, White, Red
                    }
                }
            }
        } else {
            Write-Color -Text '[i]', "[$ApplicationName]", "LogMaximum is set to 0 (Unlimited). No log files will be deleted." -Color Yellow, DarkGray, DarkCyan
        }
    }
}
function Test-AppsMonitoringSettings {
    [cmdletbinding()]
    param(
        [System.Collections.IDictionary] $Email,
        [string] $ComparisonType,
        [int[]] $DaysToExpire
    )

    $ErrorsFound = if ($Email) {
        if (-not $Email.From) {
            Write-Color -Text "[e] ", "Email.From is missing. It's required to be able to send emails" -Color Yellow, Red
            $false
        }
        if (-not $Email.To) {
            Write-Color -Text "[e] ", "Email.To is missing. It's required to be able to send emails" -Color Yellow, Red
            $false
        }
        if (-not $Email.Subject) {
            Write-Color -Text "[e] ", "Email.Subject is missing. It's required to be able to send emails" -Color Yellow, Red
            $false
        }
        if ($ComparisonType -eq 'in') {
            
        } else {
            if ($DaysToExpire.Count -gt 1) {
                Write-Color -Text "[e] ", "When using 'ComparisonType' other than 'in' only one value can be used for 'DaysToExpire'" -Color Yellow, Red
                $false
            }
        }
    }
    $ErrorsFound
}
function Start-AppsMonitoring {
    <#
    .SYNOPSIS
    Allows verification of application secrets/credentials/certificates and their expiry time and sends email to service desk, or any other email address
 
    .DESCRIPTION
    Allows verification of application secrets/credentials/certificates and their expiry time and sends email to service desk, or any other email address
 
    .PARAMETER Template
    Email template to use. You can use default template or create your own. Default template is provided, but you can change it to your liking
 
    .PARAMETER Graph
    Parameters as for Connect-MgGraph. You can use certificates or any other means of authentication.
    Graph parameter is passed as splat to Connect-MgGraph, so it's important to keep it in the same format as Connect-MgGraph expects it
 
    .PARAMETER ReportPath
    Path to report file. If not provided, report will not be generated
 
    .PARAMETER DaysToExpire
    Days to expire. Default is 7 days. You can provide multiple values, but only when using 'in' comparison type
    By default the setting is 7 days or less (le). You can change it to any other value, but you need to provide 'ComparisonType' parameter
 
    .PARAMETER ComparisonType
    Comparison type. Default is 'le' (less or equal). You can use 'eq' (equal), 'lt' (less than), 'in' (in) or 'le' (less or equal)
    Only 'in' comparison type allows multiple values for 'DaysToExpire'
 
    .PARAMETER Limit
    Limit the number of emails sent. Default is 0 (no limit)
 
    .PARAMETER AutoOpenReport
    Auto open report (HTML). Default is $false
 
    .PARAMETER Email
    Email parameters. You need to provide at least 'From', 'To', 'Subject' and MgGraphRequest ($true) parameters, assuming you're using Graph API to send emails.
 
    .PARAMETER LogPath
    Path to log file. If not provided, log will not be generated
 
    .PARAMETER LogMaximum
    Maximum number of log entries. Default is unlimited. If you want to limit the number of log entries, provide a number.
    When limit is reached, the oldest log entries will be removed.
 
    .EXAMPLE
    An example
 
    .NOTES
    General notes
    #>

    [alias('Start-O365AppsMonitoring')]
    [cmdletBinding()]
    param(
        [Alias('Body', 'EmailBody')][Parameter()][scriptblock] $Template,
        [Parameter(Mandatory)][System.Collections.IDictionary] $Graph,
        [Parameter(Mandatory)][string] $ReportPath,
        [int[]] $DaysToExpire = 7,
        [ValidateSet('eq', 'lt', 'in', 'le')][string] $ComparisonType = 'le',
        [int] $Limit,
        [switch] $AutoOpenReport,
        [parameter(Mandatory)][System.Collections.IDictionary] $Email,
        [string] $LogPath,
        [int] $LogMaximum
    )

    Set-AppsMonitoringDefaultSettings
    Set-LoggingCapabilities -LogPath $LogPath -LogMaximum $LogMaximum -ApplicationName 'AppMonitoring'

    $TestSuccess = Test-AppsMonitoringSettings
    if ($TestSuccess -contains $false) {
        return
    }

    # lets remove subject from email and use it as a template
    $TemplateSubject = $Email.Subject
    $Email.Remove('Subject')

    Write-Color -Text "[i]", "[AppMonitoring] ", "Connecting to Graph API" -Color Yellow, DarkGray, Yellow
    try {
        Connect-MgGraph @Graph -ErrorAction Stop
    } catch {
        # Write-Error -Message "Failed to connect to Graph API. Error: $($_.Exception.Message)"
        Write-Color -Text "[e]", "[AppMonitoring] ", "Failed to connect to Graph API. Error: ", "$($_.Exception.Message)" -Color Red, DarkGray, Yellow, Red
        return
    }
    Write-Color -Text "[i]", "[AppMonitoring] ", "Gathering data from Entra ID about all applications and their credentials" -Color Yellow, DarkGray, Yellow
    $GraphData = Invoke-MyGraphEssentials -Type Apps, AppsCredentials -PassThru -FilePath $ReportPath -HideHTML:(-not $AutoOpenReport)

    # we need to convert this to int, when we're not using 'in' comparison
    if ($ComparisonType -eq 'in') {
        [Array] $DaysToExpireToCompare = $DaysToExpire
    } else {
        [int] $DaysToExpireToCompare = $DaysToExpire | ForEach-Object { $_ }
    }

    $ExpiringAplications = foreach ($Application in $GraphData.Apps.Data) {
        if ($ComparisonType -eq 'lt') {
            if ($Application.KeysExpired -ne 'Not available' -and $Application.DaysToExpireOldest -lt $DaysToExpireToCompare) {
                $Application
            }
        } elseif ($ComparisonType -eq 'in') {
            if ($Application.KeysExpired -ne 'Not available' -and $Application.DaysToExpireOldest -in $DaysToExpireToCompare) {
                $Application
            }
        } elseif ($ComparisonType -eq 'le') {
            if ($Application.KeysExpired -ne 'Not available' -and $Application.DaysToExpireOldest -le $DaysToExpireToCompare) {
                $Application
            }
        } else {
            if ($Application.KeysExpired -ne 'Not available' -and $Application.DaysToExpireOldest -eq $DaysToExpireToCompare) {
                $Application
            }
        }
    }
    $CachedCredentials = [ordered] @{}
    foreach ($Credentials in $GraphData.AppsCredentials.Data) {
        if (-not $CachedCredentials[$Credentials.ObjectId]) {
            $CachedCredentials[$Credentials.ObjectId] = [System.Collections.Generic.List[object]]::new()
        }
        $CachedCredentials[$Credentials.ObjectId].Add($Credentials)
    }

    $Count = 0
    foreach ($App in $ExpiringAplications) {
        $AppCredentials = $CachedCredentials[$App.ObjectID]

        $SourceParameters = @{
            App            = $App
            AppCredentials = $AppCredentials
        }
        # creating body from a template
        $Body = EmailBody -EmailBody $Template -Parameter $SourceParameters
        # creating subject from a template
        $TemporarySubject = $ExecutionContext.InvokeCommand.ExpandString($TemplateSubject)

        Write-Color -Text "[i]", "[AppMonitoring] ", "Sending email to $($Email.To) about $($App.ApplicationName) with subject $($TemporarySubject)" -Color Yellow, Gray
        $EmailResponse = Send-EmailMessage @Email -Body $Body -Subject $TemporarySubject
        if ($EmailResponse.Status -eq $true) {
            Write-Color -Text "[+]", "[AppMonitoring] ", "Email sent successfully to ", "$($EmailResponse.SentTo)", ". Time to execute: ", "$($EmailResponse.TimeToExecute)" -Color Yellow, Green, Yellow, Green, Yellow
        } else {
            Write-Color -Text "[e]", "[AppMonitoring] ", "Email failed to send to ", "$($EmailResponse.SentTo)", ". Error: ", "$($EmailResponse.Error)", ". Time to execute: ", "$($EmailResponse.TimeToExecute)" -Color Yellow, Red, Yellow, Red, Yellow, Red
        }
        $Count++
        if ($Limit -and $Count -ge $Limit) {
            break
        }
    }
}


# Export functions and aliases as required
Export-ModuleMember -Function @('Start-AppsMonitoring') -Alias @('Start-O365AppsMonitoring')
# SIG # Begin signature block
# MIItsQYJKoZIhvcNAQcCoIItojCCLZ4CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBAOMcww/sAVvX8
# M8+mYAQR6gvTOqDL5TXNe/XrW23gc6CCJrQwggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG
# SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw
# aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK
# EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm
# dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu
# d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD
# eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1
# XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld
# QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS
# YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm
# M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT
# QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx
# fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD
# VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq
# hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4
# XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ
# aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg
# X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk
# apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL
# FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy
# 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u
# KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54
# zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8
# 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8
# aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w
# ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1
# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG
# SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS
# g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9
# /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn
# HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0
# VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f
# sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj
# gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0
# QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv
# mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T
# /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk
# 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r
# mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E
# FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n
# P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG
# CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu
# Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln
# aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v
# Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV
# HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB
# AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp
# wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl
# zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ
# cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe
# Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j
# Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh
# IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6
# OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw
# N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR
# 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2
# VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGsDCCBJigAwIBAgIQ
# CK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEV
# MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t
# MSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAw
# MDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT
# aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+k
# jmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9
# NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9
# URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegY
# E2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS
# 4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJa
# wv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+w
# c86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eR
# Gv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF2
# 3r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCK
# ZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhEC
# AwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2
# O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P
# MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcB
# AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr
# BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1
# c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAH
# BgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6
# mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/
# SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzY
# gBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9
# kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ
# 9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAew
# Q3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5Lm
# Tl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HA
# SIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xr
# y7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhR
# ILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFu
# v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGwjCCBKqgAwIBAgIQBUSv85SdCDmmv9s/
# X+VhFjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5
# NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIzMDcxNDAwMDAwMFoXDTM0MTAx
# MzIzNTk1OVowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu
# MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMzCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBAKNTRYcdg45brD5UsyPgz5/X5dLnXaEOCdwvSKOX
# ejsqnGfcYhVYwamTEafNqrJq3RApih5iY2nTWJw1cb86l+uUUI8cIOrHmjsvlmbj
# aedp/lvD1isgHMGXlLSlUIHyz8sHpjBoyoNC2vx/CSSUpIIa2mq62DvKXd4ZGIX7
# ReoNYWyd/nFexAaaPPDFLnkPG2ZS48jWPl/aQ9OE9dDH9kgtXkV1lnX+3RChG4PB
# uOZSlbVH13gpOWvgeFmX40QrStWVzu8IF+qCZE3/I+PKhu60pCFkcOvV5aDaY7Mu
# 6QXuqvYk9R28mxyyt1/f8O52fTGZZUdVnUokL6wrl76f5P17cz4y7lI0+9S769Sg
# LDSb495uZBkHNwGRDxy1Uc2qTGaDiGhiu7xBG3gZbeTZD+BYQfvYsSzhUa+0rRUG
# FOpiCBPTaR58ZE2dD9/O0V6MqqtQFcmzyrzXxDtoRKOlO0L9c33u3Qr/eTQQfqZc
# ClhMAD6FaXXHg2TWdc2PEnZWpST618RrIbroHzSYLzrqawGw9/sqhux7UjipmAmh
# cbJsca8+uG+W1eEQE/5hRwqM/vC2x9XH3mwk8L9CgsqgcT2ckpMEtGlwJw1Pt7U2
# 0clfCKRwo+wK8REuZODLIivK8SgTIUlRfgZm0zu++uuRONhRB8qUt+JQofM604qD
# y0B7AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAW
# BgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglg
# hkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0O
# BBYEFKW27xPn783QZKHVVqllMaPe1eNJMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6
# Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEy
# NTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZT
# SEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAIEa1t6g
# qbWYF7xwjU+KPGic2CX/yyzkzepdIpLsjCICqbjPgKjZ5+PF7SaCinEvGN1Ott5s
# 1+FgnCvt7T1IjrhrunxdvcJhN2hJd6PrkKoS1yeF844ektrCQDifXcigLiV4JZ0q
# BXqEKZi2V3mP2yZWK7Dzp703DNiYdk9WuVLCtp04qYHnbUFcjGnRuSvExnvPnPp4
# 4pMadqJpddNQ5EQSviANnqlE0PjlSXcIWiHFtM+YlRpUurm8wWkZus8W8oM3NG6w
# QSbd3lqXTzON1I13fXVFoaVYJmoDRd7ZULVQjK9WvUzF4UbFKNOt50MAcN7MmJ4Z
# iQPq1JE3701S88lgIcRWR+3aEUuMMsOI5ljitts++V+wQtaP4xeR0arAVeOGv6wn
# LEHQmjNKqDbUuXKWfpd5OEhfysLcPTLfddY2Z1qJ+Panx+VPNTwAvb6cKmx5Adza
# ROY63jg7B145WPR8czFVoIARyxQMfq68/qTreWWqaNYiyjvrmoI1VygWy2nyMpqy
# 0tg6uLFGhmu6F/3Ed2wVbK6rr3M66ElGt9V/zLY4wNjsHPW2obhDLN9OTH0eaHDA
# dwrUAuBcYLso/zjlUlrWrBciI0707NMX+1Br/wd3H3GXREHJuEbTbDJ8WC9nR2Xl
# G3O2mflrLAZG70Ee8PBf4NvZrZCARK+AEEGKMIIHXzCCBUegAwIBAgIQB8JSdCgU
# otar/iTqF+XdLjANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UE
# ChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQg
# Q29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIzMDQxNjAw
# MDAwMFoXDTI2MDcwNjIzNTk1OVowZzELMAkGA1UEBhMCUEwxEjAQBgNVBAcMCU1p
# a2/FgsOzdzEhMB8GA1UECgwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMSEwHwYD
# VQQDDBhQcnplbXlzxYJhdyBLxYJ5cyBFVk9URUMwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQCUmgeXMQtIaKaSkKvbAt8GFZJ1ywOH8SwxlTus4McyrWmV
# OrRBVRQA8ApF9FaeobwmkZxvkxQTFLHKm+8knwomEUslca8CqSOI0YwELv5EwTVE
# h0C/Daehvxo6tkmNPF9/SP1KC3c0l1vO+M7vdNVGKQIQrhxq7EG0iezBZOAiukNd
# GVXRYOLn47V3qL5PwG/ou2alJ/vifIDad81qFb+QkUh02Jo24SMjWdKDytdrMXi0
# 235CN4RrW+8gjfRJ+fKKjgMImbuceCsi9Iv1a66bUc9anAemObT4mF5U/yQBgAuA
# o3+jVB8wiUd87kUQO0zJCF8vq2YrVOz8OJmMX8ggIsEEUZ3CZKD0hVc3dm7cWSAw
# 8/FNzGNPlAaIxzXX9qeD0EgaCLRkItA3t3eQW+IAXyS/9ZnnpFUoDvQGbK+Q4/bP
# 0ib98XLfQpxVGRu0cCV0Ng77DIkRF+IyR1PcwVAq+OzVU3vKeo25v/rntiXCmCxi
# W4oHYO28eSQ/eIAcnii+3uKDNZrI15P7VxDrkUIc6FtiSvOhwc3AzY+vEfivUkFK
# RqwvSSr4fCrrkk7z2Qe72Zwlw2EDRVHyy0fUVGO9QMuh6E3RwnJL96ip0alcmhKA
# BGoIqSW05nXdCUbkXmhPCTT5naQDuZ1UkAXbZPShKjbPwzdXP2b8I9nQ89VSgQID
# AQABo4ICAzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYD
# VR0OBBYEFHrxaiVZuDJxxEk15bLoMuFI5233MA4GA1UdDwEB/wQEAwIHgDATBgNV
# HSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3Js
# My5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQw
# OTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAy
# MUNBMS5jcmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0
# cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggr
# BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBo
# dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2Rl
# U2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqG
# SIb3DQEBCwUAA4ICAQC3EeHXUPhpe31K2DL43Hfh6qkvBHyR1RlD9lVIklcRCR50
# ZHzoWs6EBlTFyohvkpclVCuRdQW33tS6vtKPOucpDDv4wsA+6zkJYI8fHouW6Tqa
# 1W47YSrc5AOShIcJ9+NpNbKNGih3doSlcio2mUKCX5I/ZrzJBkQpJ0kYha/pUST2
# CbE3JroJf2vQWGUiI+J3LdiPNHmhO1l+zaQkSxv0cVDETMfQGZKKRVESZ6Fg61b0
# djvQSx510MdbxtKMjvS3ZtAytqnQHk1ipP+Rg+M5lFHrSkUlnpGa+f3nuQhxDb7N
# 9E8hUVevxALTrFifg8zhslVRH5/Df/CxlMKXC7op30/AyQsOQxHW1uNx3tG1DMgi
# zpwBasrxh6wa7iaA+Lp07q1I92eLhrYbtw3xC2vNIGdMdN7nd76yMIjdYnAn7r38
# wwtaJ3KYD0QTl77EB8u/5cCs3ShZdDdyg4K7NoJl8iEHrbqtooAHOMLiJpiL2i9Y
# n8kQMB6/Q6RMO3IUPLuycB9o6DNiwQHf6Jt5oW7P09k5NxxBEmksxwNbmZvNQ65Z
# n3exUAKqG+x31Egz5IZ4U/jPzRalElEIpS0rgrVg8R8pEOhd95mEzp5WERKFyXhe
# 6nB6bSYHv8clLAV0iMku308rpfjMiQkqS3LLzfUJ5OHqtKKQNMLxz9z185UCszGC
# BlMwggZPAgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
# bmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBS
# U0E0MDk2IFNIQTM4NCAyMDIxIENBMQIQB8JSdCgUotar/iTqF+XdLjANBglghkgB
# ZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJ
# AzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8G
# CSqGSIb3DQEJBDEiBCDygcwRzO+PDBMDKCCngTZTzQ4QzxzmvM5dmIT6c4F3VzAN
# BgkqhkiG9w0BAQEFAASCAgAgyYNLZ5T/p0UJWWYDdtbX1VQ4k6+rVBbOMgBlyV4r
# mKEAZFxGK3uO01mL6m1z2Z/QQVPuyvacFaMBlrZgWK6xoyWC10ZPvvoV5bEfxOo9
# IhDwVAHOoYMCFS8wPURA03qN/vmGnpUfmIov54GBKgX6Xy5nTcWU0nyVd87QfiIj
# Mvjtp+Nq/3RCmt0pvM+0QlDAy9YJqNe1I8rtnUSBlfPwnmznGre0vn+3JYT3r/sw
# nT9OzAsXNxuq5KR8q0cFS8be5qbsHWggNZaNn9SddyRJ7NjC/J4qIpfTh2gei6lR
# H6FGnT877j+pWT5E4DVRJH3wSzZDNYWtIM6p+svEY5fuqNdvKb3WJjIk2m9jeUaR
# D04krdDO/uaj8qPB2K9R0EwjtaWiJ3tC70/k4iko9WSu4trvUB027KAWswaunPfD
# 0+l97vNSNvXTFt/hhB3OjZVb3t6XNuDwzbAvFBc/DK0dl3Kzkhimy7Ft+jwWuBmw
# LKVdJiil03PEFiEWtyAR0e1NR+ef+vvhYT8Uc5P95sU+Hdblq2iARwYed+QzMqiY
# dQfhHWpogTSc9pBLZgaVkeVmNMLQDxxOwTQWosKQO0gNhu5vSwJ27rFcXAGWkLzS
# N7Cr5Vvhzg9G1Jp8oyRQ19k+Udw3WiajwZGu4RK9AJ+UE36w/Tfe/hIVLxuo7USO
# M6GCAyAwggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1
# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAVEr/OUnQg5
# pr/bP1/lYRYwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN
# AQcBMBwGCSqGSIb3DQEJBTEPFw0yNDA1MTQxOTI2NTRaMC8GCSqGSIb3DQEJBDEi
# BCB2Wxq4nURcEzsuX5nscptqRu6T07L6BfSOxIgwrvEmbzANBgkqhkiG9w0BAQEF
# AASCAgCbI/X4M/ky0RLaTs+0aTe4bGlGH23cfzqJgVUcRwj4YMM/EUPNgMl8BIdg
# KcBsStRFUnZcxVeOQfJ+TnTMAOYOgyFmDvJxqfJZCummO0kyRqquoFjTmK9qzS+6
# gc5099DMGLD6B53uy+dNOkLzk8n3R0GnikDQ16PEfi1kxT4LvzF4+DzZm3nbn4QD
# Qe6wJ3jKcVU4NvDbQ6krtGGf6f8zag0VVtb5obZyTwktJwLF9omJjhdbwetCGR6E
# 9RZHZ7diSquQ9a1lWTMXBpuV5OkBdzCLF1c8WW6iin1v8BBSQSvaDEsPih/p1WZF
# bBKniNcL45TKOIbj3CtMh121GUPQiCQLSfvPOTfsqQQb+GFhSV1okqGbdPOvR4Hd
# zYennFX3wo3gFlgIrPFGGi8/wNME84Oqyh67q9sba8E9ST9X+HtMNGDHGeymkGSz
# VxOpnCri5hpcY3ftvkcSU8jXvlaAvy0vbEFlSeoT83Tjq6IiCtr+hFeZZshSBC48
# g5Z3Ls1hMPypUMRD92W7dqH0+t9Xz/6ycZbYrsqo/0v+RSLWeZpNX6ck3/uAZbX4
# 8q6cFCrX1YWHvJewKnFYI/mmnqMwW0x5YDSd4BOt7P6AIhP0SaOSFGWfId3BqVgk
# LplfIrX+Nw324Y10vBKMsfdB4FBTXlJAERhiLz5k5nsF4TS/Zw==
# SIG # End signature block