wrt.helpers.psm1


[Flags()] enum ProductFlags
{
   SignatureStatus = 0x000000F0
   ProductOwner    = 0x00000F00
   ProductState    = 0x0000F000
}

[Flags()] enum ProductOwner
{
    NonMs        = 0x000
    Windows      = 0x100
}

[Flags()] enum ProductState
{
    Off         = 0x0000
    On          = 0x1000
    Snoozed     = 0x2000
    Expired     = 0x3000
}

[Flags()] enum SignatureStatus
{
    UpToDate     = 0x00
    OutOfDate    = 0x10
}

<#
.SYNOPSIS
    Short description
.DESCRIPTION
    Long description
.EXAMPLE
    PS C:\> <example usage>
    Explanation of what the example does
.INPUTS
    Inputs (if any)
.OUTPUTS
    Output (if any)
.NOTES
    General notes
#>

# Create the function to create the authorization signature that is used by Post-LogAnalyticsData
Function Build-Signature ($customerId, $sharedKey, $date, $contentLength, $method, $contentType, $resource)
{
    $xHeaders = "x-ms-date:" + $date
    $stringToHash = $method + "`n" + $contentLength + "`n" + $contentType + "`n" + $xHeaders + "`n" + $resource

    $bytesToHash = [Text.Encoding]::UTF8.GetBytes($stringToHash)
    $keyBytes = [Convert]::FromBase64String($sharedKey)

    $sha256 = New-Object System.Security.Cryptography.HMACSHA256
    $sha256.Key = $keyBytes
    $calculatedHash = $sha256.ComputeHash($bytesToHash)
    $encodedHash = [Convert]::ToBase64String($calculatedHash)
    $authorization = 'SharedKey {0}:{1}' -f $customerId,$encodedHash
    return $authorization
}

<#
.SYNOPSIS
Compare PS Objects and return them with a clear equality sign
.DESCRIPTION
This function is used for comparison to see if an object needs to be updated
.PARAMETER ReferenceObject
Reference object is the data of the object as present or persistent
.PARAMETER DifferenceObject
Difference object is data that is or is expected to be altered
.EXAMPLE
Compare-PSObject -ReferenceObject $PSObject1 -ReferenceObject $PSObject2
.NOTES
NAME: Compare-PSObject
#>


function Compare-PSObject {

    [CmdletBinding()]
    param (
        # Reference value is the Online available
        [Parameter(Mandatory)]
        [psobject]$ReferenceObject,

        # Difference template is the template that will be uploaded
        [Parameter(Mandatory)]
        [psobject]$DifferenceObject
    )

    process {
        $objprops = $ReferenceObject | Get-Member -MemberType Property, NoteProperty | ForEach-Object Name
        $objprops += $DifferenceObject | Get-Member -MemberType Property, NoteProperty | ForEach-Object Name
        $objprops = $objprops | Sort-Object -Unique | Select-Object

        $diffs = @()

        foreach ($objprop in $objprops) {
            $diff = Compare-Object $ReferenceObject $DifferenceObject -Property $objprop
            if ($diff) {
                $diffprops = @{
                    PropertyName = $objprop
                    RefValue     = ($diff | Where-Object { $_.SideIndicator -eq '<=' } | ForEach-Object $($objprop))
                    DiffValue    = ($diff | Where-Object { $_.SideIndicator -eq '=>' } | ForEach-Object $($objprop))
                }
                $diffs += New-Object PSObject -Property $diffprops
            }
        }
        if ($diffs) {
            return ($diffs | Select-Object PropertyName, RefValue, DiffValue)
        }
    }
}

function Add-ProductStates {
<#
.SYNOPSIS
    Adds statuses to a product PSObject.

.DESCRIPTION
    Adds statuses to a product PSObject. If this function is used
    in a command pipeline it will add properties to the resulting PSObject containing boolean properties. These properties reflect
    status of Product Enablement and if it is updated. These values are calculated using the State DWORD using bitfields.

.PARAMETER ProductState
    The value (DWORD) containing the bitflags.

.PARAMETER Products
    PSObject containing object array of Microsoft.Management.Infrastructure.CimInstance#ROOT/SecurityCenter2/AntiVirusProduct

.EXAMPLE
    PS C:\Users\maurice> Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntivirusProduct | Add-ProductStates

    enabled : True
    displayName : Trend Micro Antivirus+
    instanceGuid : {AFEE279F-FAE7-BAEE-3A88-4BF7277B8551}
    pathToSignedProductExe : C:\Program Files\Trend Micro\Titanium\TmWscSvc\wschandler.exe
    pathToSignedReportingExe : C:\Program Files\Trend Micro\Titanium\TmWscSvc\WSCStatusController.exe
    productState : 266240
    timestamp : Sun, 12 Apr 2020 15:09:56 GMT
    PSComputerName :

    enabled : True
    displayName : Sophos Home
    instanceGuid : {FFADE7EA-DC92-4602-D6B2-626CD3450A0F}
    pathToSignedProductExe : C:\Program Files (x86)\Sophos\Sophos Anti-Virus\WSCClient.exe
    pathToSignedReportingExe : C:\Program Files (x86)\Sophos\Sophos Anti-Virus\WSCClient.exe
    productState : 331776
    timestamp : Sun, 12 Apr 2020 15:18:39 GMT
    PSComputerName :

    enabled : False
    displayName : Windows Defender
    instanceGuid : {D68DDC3A-831F-4fae-9E44-DA132C1ACF46}
    pathToSignedProductExe : windowsdefender://
    pathToSignedReportingExe : %ProgramFiles%\Windows Defender\MsMpeng.exe
    productState : 393472
    timestamp : Sun, 12 Apr 2020 15:08:57 GMT
    PSComputerName :

.EXAMPLE
    PS C:\Users\maurice> $products = Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntivirusProduct
    PS C:\Users\maurice> Add-ProductStates -Products $products

    enabled : True
    displayName : Trend Micro Antivirus+
    instanceGuid : {AFEE279F-FAE7-BAEE-3A88-4BF7277B8551}
    pathToSignedProductExe : C:\Program Files\Trend Micro\Titanium\TmWscSvc\wschandler.exe
    pathToSignedReportingExe : C:\Program Files\Trend Micro\Titanium\TmWscSvc\WSCStatusController.exe
    productState : 266240
    timestamp : Sun, 12 Apr 2020 15:09:56 GMT
    PSComputerName :

    enabled : True
    displayName : Sophos Home
    instanceGuid : {FFADE7EA-DC92-4602-D6B2-626CD3450A0F}
    pathToSignedProductExe : C:\Program Files (x86)\Sophos\Sophos Anti-Virus\WSCClient.exe
    pathToSignedReportingExe : C:\Program Files (x86)\Sophos\Sophos Anti-Virus\WSCClient.exe
    productState : 331776
    timestamp : Sun, 12 Apr 2020 15:18:39 GMT
    PSComputerName :

    enabled : False
    displayName : Windows Defender
    instanceGuid : {D68DDC3A-831F-4fae-9E44-DA132C1ACF46}
    pathToSignedProductExe : windowsdefender://
    pathToSignedReportingExe : %ProgramFiles%\Windows Defender\MsMpeng.exe
    productState : 393472
    timestamp : Sun, 12 Apr 2020 15:08:57 GMT
    PSComputerName :

.EXAMPLE
    PS C:\Users\maurice> (Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntivirusProduct)[0].productState | Add-ProductStates

    enabled : True

.EXAMPLE
    PS C:\Users\maurice> $prodState = (Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntivirusProduct)[0].productState
    PS C:\Users\maurice> Add-ProductStates -ProductState $prodState

    enabled : True

.NOTES
    This function utilizes Test-IsProductEnabled, ... To enrich information on State.
#>

    [CmdletBinding()]
    param (
        # This parameter can be passed from pipeline and can contain and array of collections that contain State or productstate members
        [Parameter(ValueFromPipeline)]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $Products,
        # Product State contains a value (DWORD) that contains multiple bitflags and we use the productState flag (0000F000)
        [Parameter(Position = 0, ValueFromPipelineByPropertyName, ValueFromPipeline, HelpMessage = "The value (DWORD) containing the bitflags.")]
        [Alias("STATE")]
        [UInt32]$ProductState
    )

    begin {
        $results = $null
    }

    process {
        If ($Products -is [array]) {
            If ($Products.Count -gt 0) {
                If (Get-Member -inputobject $Products[0] -name "productState" -Membertype Properties) {
                    $results += $Products.PSObject.Copy()
                    foreach ($item in $Products) {
                        If($results.Where({$_.instanceGuid -eq $item.instanceGuid}).Properties.name -notmatch "state") {
                            $results.Where({$_.instanceGuid -eq $item.instanceGuid}) |
                                Add-Member -NotePropertyName state -NotePropertyValue $([ProductState]($item.productState -band [ProductFlags]::ProductState))
                        }
                        else {
                            Write-Error 'Could not add state property it already exists...'
                        }
                        If($results.Where({$_.instanceGuid -eq $item.instanceGuid}).Properties.name -notmatch "signatureStatus") {
                            $results.Where({$_.instanceGuid -eq $item.instanceGuid}) |
                                Add-Member -NotePropertyName signatureStatus -NotePropertyValue $([SignatureStatus]($item.productState -band [ProductFlags]::SignatureStatus))
                        }
                        else {
                            Write-Error 'Could not add signatureStatus property it already exists...'
                        }
                    }
                }
            }
        }
        If ($ProductState -and (-not $Products)) {
            If($results.Properties.name -notmatch "enabled") {
                $results += New-Object PSObject -Property @{
                        state = $([ProductState]($item.productState -band [ProductFlags]::ProductState))
                        signatureStatus = $([SignatureStatus]($item.productState -band [ProductFlags]::SignatureStatus))
                }
            }
        }
    }

    end {
        If($results) {
            return $results
        }
    }
}

Function Deploy-CompressedFile
{
[CmdletBinding()]
param (
    [Parameter()]
    [string]
    $Url,

    [Parameter()]
    [string]
    $CorrelationId,

    # if should be logged to LogAnalytics
    [Parameter()]
    [switch]
    $Log
)
    $PSDefaultParameterValues = $Global:PSDefaultParameterValues
    $ProgressPreference = "SilentlyContinue"
    $targetPath = "$env:TEMP\{$correlationId}"
    $fullpathZip = "$env:TEMP\{$correlationId}.zip"
    try
    {
        Invoke-WebRequest $Url -OutFile $fullpathZip -ErrorAction Stop
        If($Log) { Send-ToLogAnalytics -message "The compressed uninstall package from Azure Blob to the local computer, $fullpathZip" }
    }
    catch
    {
        If($Log) { Send-ToLogAnalytics -message "Uninstall package could not be downloaded $($_.Exception) $($_.ErrorDetails)" }
        return $false
    }

    try
    {
        Expand-Archive -Path $fullpathZip -DestinationPath $targetPath -Force -ErrorAction Stop
        If($Log) { Send-ToLogAnalytics -message "Extracted the compressed uninstall package to: $targetPath" }
    }
    catch
    {
        If($Log) { Send-ToLogAnalytics -message "Uninstall package could not be extracted $($_.Exception) $($_.ErrorDetails)" }
       return $false
    }
    return $true
}

function Deploy-File
{
    [CmdletBinding()]
    param (
        # Url of the location of the script
        [Parameter()]
        [string]
        $Url,

        [Parameter()]
        [string]
        $CorrelationId,

        # if should be logged to LogAnalytics
        [Parameter()]
        [switch]
        $Log
    )
    $PSDefaultParameterValues = $Global:PSDefaultParameterValues
    $ProgressPreference = "SilentlyContinue"
    $targetPath = "$env:TEMP\{$correlationId}"

    try
    {
        #Invoke-WebRequest $item -OutFile $($targetPath + $item.Split('/')[-1]) -ErrorAction Stop
        $outFile = $($targetPath + "\$correlationId.ps1")
        Invoke-WebRequest $Url -OutFile $outFile -ErrorAction Stop
        If($Log) { Send-ToLogAnalytics -message "PS1File is downloaded." }
        return $outFile
    }
    catch {
        If($Log) { Send-ToLogAnalytics -message "Could not download PS1File. $($_.Exception) $($_.ErrorDetails)"  }
        return $false
    }
}

Function Find-File
{
    [CmdletBinding()]
    param (

        # Specifies a path to one location. Unlike the Path parameter, the value of the LiteralPath parameter is
        # used exactly as it is typed. No characters are interpreted as wildcards. If the path includes escape characters,
        # enclose it in single quotation marks. Single quotation marks tell Windows PowerShell not to interpret any
        # characters as escape sequences.
        [Parameter(Mandatory=$true,
                   Position=0,
                   ParameterSetName="LiteralPath",
                   ValueFromPipelineByPropertyName=$true,
                   HelpMessage="Literal path to one location.")]
        [Alias("PSPath")]
        [ValidateNotNullOrEmpty()]
        [string]
        $LiteralPath,

        #filename to search recursively in LiteralPath for
        [Parameter()]
        [string]
        $filename,

        # if should be logged to LogAnalytics
        [Parameter()]
        [switch]
        $Log
    )
    $PSDefaultParameterValues = $Global:PSDefaultParameterValues
    try
    {
        $filefound = Get-ChildItem -Path $LiteralPath -Filter $filename -Recurse -ErrorAction Stop
        $result = $filefound[0].FullName
    }
    catch
    {
        If($Log) { Write-LogAnalyticsMessage -message "Could not locate $filename in $LiteralPath " }
    }
    return $result
}

<#
.SYNOPSIS
    Removes a complete key from the registry
.DESCRIPTION
    Removes a complete key from the registry and returns true or false if successfull or not
.EXAMPLE
    PS C:\> Remove-RegistryKey -Path "HKLM:\testkey"
    Removes the Key HKLM:\testkey
.NOTES
    General notes
.OUTPUTS
    Boolean value which corresponds to the outcome
#>

function Remove-RegistryKey
{
    param (
        # the path to the value
        [Parameter()]
        [string]
        $KeyPath,

        # if should be logged to LogAnalytics
        [Parameter()]
        [switch]
        $Log
    )
    $PSDefaultParameterValues = $Global:PSDefaultParameterValues
    If(Test-Path $KeyPath)
    {
        try
        {
            Remove-Item -Path $KeyPath -ErrorAction Stop -Recurse -Force
            If($Log) { Send-ToLogAnalytics -message "Removed $KeyPath." }
        }
        catch
        {
            If($Log) { Send-ToLogAnalytics -message "Could not remove $KeyPath. $($_.Exception) $($_.ErrorDetails)" }
        }
    }
}

<#
.SYNOPSIS
    Removes a value from the registry
.DESCRIPTION
    Removes a value from the registry and returns true or false if successfull or not
.EXAMPLE
    PS C:\> Remove-Registry -Path "HKLM:\testkey" -Value "testValue"
    Removes the value testValue from HKLM:\testkey
.NOTES
    General notes
.OUTPUTS
    Boolean value which corresponds to the outcome
#>

function Remove-RegistryValue
{
    param (
        # the path to the value
        [Parameter()]
        [string]
        $Path,

        # the name of the value
        [Parameter()]
        [string]
        $Value,

        # if should be logged to LogAnalytics
        [Parameter()]
        [switch]
        $Log
    )
    $PSDefaultParameterValues = $Global:PSDefaultParameterValues
    If(Test-Path $Path)
    {
        If(Test-RegistryValue -Path $Path -Value $RegistryValue)
        {
            try
            {
                Remove-ItemProperty -Path $Path -Name $RegistryValue -ErrorAction Stop
                If($Log) { Send-ToLogAnalytics -message "Removed $Path in path $RegistryPath." }
            }
            catch
            {
                If($Log) { Send-ToLogAnalytics -message "Could not remove $Path in path $RegistryPath. $($_.Exception) $($_.ErrorDetails)" }
            }
        }
    }
}

function Search-Registry {
    <#
    .SYNOPSIS
    Searches registry key names, value names, and value data (limited).

    .DESCRIPTION
    This function can search registry key names, value names, and value data (in a limited fashion). It outputs custom objects that contain the key and the first match type (KeyName, ValueName, or ValueData).

    .EXAMPLE
    Search-Registry -Path HKLM:\SYSTEM\CurrentControlSet\Services\* -SearchRegex "svchost" -ValueData

    .EXAMPLE
    Search-Registry -Path HKLM:\SOFTWARE\Microsoft -Recurse -ValueNameRegex "ValueName1|ValueName2" -ValueDataRegex "ValueData" -KeyNameRegex "KeyNameToFind1|KeyNameToFind2"
    .NOTES
    from: https://gallery.technet.microsoft.com/scriptcenter/Search-Registry-Find-Keys-b4ce08b4
    Thanks to: https://social.technet.microsoft.com/profile/rohn%20edwards/
    #>

        [CmdletBinding()]
        param(
            [Parameter(Mandatory, Position=0, ValueFromPipelineByPropertyName)]
            [Alias("PsPath")]
            # Registry path to search
            [string[]] $Path,
            # Specifies whether or not all subkeys should also be searched
            [switch] $Recurse,
            [Parameter(ParameterSetName="SingleSearchString", Mandatory)]
            # A regular expression that will be checked against key names, value names, and value data (depending on the specified switches)
            [string] $SearchRegex,
            [Parameter(ParameterSetName="SingleSearchString")]
            # When the -SearchRegex parameter is used, this switch means that key names will be tested (if none of the three switches are used, keys will be tested)
            [switch] $KeyName,
            [Parameter(ParameterSetName="SingleSearchString")]
            # When the -SearchRegex parameter is used, this switch means that the value names will be tested (if none of the three switches are used, value names will be tested)
            [switch] $ValueName,
            [Parameter(ParameterSetName="SingleSearchString")]
            # When the -SearchRegex parameter is used, this switch means that the value data will be tested (if none of the three switches are used, value data will be tested)
            [switch] $ValueData,
            [Parameter(ParameterSetName="MultipleSearchStrings")]
            # Specifies a regex that will be checked against key names only
            [string] $KeyNameRegex,
            [Parameter(ParameterSetName="MultipleSearchStrings")]
            # Specifies a regex that will be checked against value names only
            [string] $ValueNameRegex,
            [Parameter(ParameterSetName="MultipleSearchStrings")]
            # Specifies a regex that will be checked against value data only
            [string] $ValueDataRegex
        )

        begin {
            switch ($PSCmdlet.ParameterSetName) {
                SingleSearchString {
                    $NoSwitchesSpecified = -not ($PSBoundParameters.ContainsKey("KeyName") -or $PSBoundParameters.ContainsKey("ValueName") -or $PSBoundParameters.ContainsKey("ValueData"))
                    if ($KeyName -or $NoSwitchesSpecified) { $KeyNameRegex = $SearchRegex }
                    if ($ValueName -or $NoSwitchesSpecified) { $ValueNameRegex = $SearchRegex }
                    if ($ValueData -or $NoSwitchesSpecified) { $ValueDataRegex = $SearchRegex }
                }
                MultipleSearchStrings {
                    # No extra work needed
                }
            }
        }

        process {
            foreach ($CurrentPath in $Path) {
                Get-ChildItem $CurrentPath -Recurse:$Recurse |
                    ForEach-Object {
                        $Key = $_

                        if ($KeyNameRegex) {
                            Write-Verbose ("{0}: Checking KeyNamesRegex" -f $Key.Name)

                            if ($Key.PSChildName -match $KeyNameRegex) {
                                Write-Verbose " -> Match found!"
                                return [PSCustomObject] @{
                                    Key = $Key
                                    Reason = "KeyName"
                                }
                            }
                        }

                        if ($ValueNameRegex) {
                            Write-Verbose ("{0}: Checking ValueNamesRegex" -f $Key.Name)

                            if ($Key.GetValueNames() -match $ValueNameRegex) {
                                Write-Verbose " -> Match found!"
                                return [PSCustomObject] @{
                                    Key = $Key
                                    Reason = "ValueName"
                                }
                            }
                        }

                        if ($ValueDataRegex) {
                            Write-Verbose ("{0}: Checking ValueDataRegex" -f $Key.Name)

                            if (($Key.GetValueNames() | % { $Key.GetValue($_) }) -match $ValueDataRegex) {
                                Write-Verbose " -> Match!"
                                return [PSCustomObject] @{
                                    Key = $Key
                                    Reason = "ValueData"
                                }
                            }
                        }
                    }
            }
        }
    }

Function Send-MessageToLocalUsers
{
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $Message,

        # timeout for message
        [Parameter(HelpMessage="Seconds until the message is automatically discarded. Default is 0, means never.")]
        [int]
        $TimeOut = 0,

        # if should be logged to LogAnalytics
        [Parameter()]
        [switch]
        $Log
    )
    $PSDefaultParameterValues = $Global:PSDefaultParameterValues
    try
    {
        Start-Process "$env:SystemRoot\System32\msg.exe" -ArgumentList "* /time $TimeOut /v '$Message' " -ErrorAction Stop
        If($Log) { Send-ToLogAnalytics -message "Message send to all users on this system." }
    }
    catch
    {
        If($Log) { Send-ToLogAnalytics -message "Could not send message." }
    }
}

<#
.SYNOPSIS
    Sends a given message to a custom log in Log Analytics workspace
.DESCRIPTION
Sends a given message to a custom log in Log Analytics workspace. Use PSDefaultParameterValues to shorten this command. This will improve readability of the script.
.EXAMPLE
    You could set some paramters once like here using $PSDefaultParameterValues
    $PSDefaultParameterValues = @{
        "Send-ToLogAnalytics:LogName"="myCustomLog";
        "Send-ToLogAnalytics:SharedKey"="<sharedKey>";
        "Send-ToLogAnalytics:CustomerId"="CustomerID or WorkspaceID";
        "Send-ToLogAnalytics:ScriptVersion"="<any value>";
        "Send-ToLogAnalytics:CorrelationId"= $(New-Guid | Select-Object -ExpandProperty Guid);
    }
    Send-ToLogAnalytics -message 'Send from Powershell to LogAnalytics using wrt.helpers Send-ToLogAnalytics'
.PARAMETER Message
    Contents if this parameter will be passed to log analytics in the collumn message
.NOTES
    Besides
#>

Function Send-ToLogAnalytics
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $Message,

        # the log name
        [Parameter(Mandatory)]
        [string]
        $LogName,

        # parameter to set a script version to the log message
        [Parameter(Mandatory)]
        [string]
        $ScriptVersion,

        # sets the correlationId to the log message, see this as a session ID this is unique to the session this script runs
        [Parameter(Mandatory)]
        [string]
        $CorrelationId,

        # sets the customer or workspaceId to which this message is send
        [Parameter(Mandatory)]
        [string]
        $CustomerId,

        # the sharedsecret which is used to as an API key
        [Parameter(Mandatory)]
        [string]
        $SharedKey
    )
    $ProgressPreference = "SilentlyContinue"
    Write-Verbose $message
    $method = "POST"
    $contentType = "application/json"
    $resource = "/api/logs"
    $rfc1123date = [DateTime]::UtcNow.ToString("r")
    $ISO8601DateTime  = [DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")

    $LogObject = New-Object -TypeName PSObject
    $LogObject | Add-Member -NotePropertyName ISO8601DateTime -NotePropertyValue $ISO8601DateTime
    $LogObject | Add-Member -NotePropertyName Computer -NotePropertyValue $($env:COMPUTERNAME)
    $LogObject | Add-Member -NotePropertyName Message -NotePropertyValue $Message
    $LogObject | Add-Member -NotePropertyName ScriptVersion -NotePropertyValue $ScriptVersion
    $LogObject | Add-Member -NotePropertyName LoggedOnUser -NotePropertyValue $loggedOnUser
    $LogObject | Add-Member -NotePropertyName CorrelationId -NotePropertyValue $CorrelationId


    $json = $LogObject | ConvertTo-Json -Compress
    $body = [System.Text.Encoding]::UTF8.GetBytes($json)

    $contentLength = $body.Length
    $signature = Build-Signature `
        -customerId $CustomerId `
        -sharedKey $SharedKey `
        -date $rfc1123date `
        -contentLength $contentLength `
        -fileName $fileName `
        -method $method `
        -contentType $contentType `
        -resource $resource
    $uri = "https://" + $CustomerId + ".ods.opinsights.azure.com" + $resource + "?api-version=2016-04-01"

    # time-generated-field The name of a field in the data that contains the timestamp of the data item. If you specify a field then its contents are used for TimeGenerated. If this field isn’t specified, the default for TimeGenerated is the time that the message is ingested. The contents of the message field should follow the ISO 8601 format YYYY-MM-DDThh:mm:ssZ.
    $headers = @{
        "Authorization" = $signature;
        "Log-Type" = $LogName;
        "x-ms-date" = $rfc1123date;
        "time-generated-field" = "ISO8601DateTime";
    }

    $response = Invoke-WebRequest -Uri $uri -Method $method -ContentType $contentType -Headers $headers -Body $body -UseBasicParsing
    Start-Sleep -Milliseconds 100
}

function Test-IsProductStateOn {
    <#
    .SYNOPSIS
        Tests if given product state has product state flag to On
    .DESCRIPTION
        Registry, WMI and other properties may contain a DWORD value or data object that represents the state of the corresponding product.
        Specific state of the product is set to a bit in this DWORD, these states can be optained using bitwise operations.
        This function will return true if the flag for product state is set to on, meaning this product is enabled.
    .PARAMETER ProductState
        The value (DWORD) containing the bitflags.
    .EXAMPLE
        PS C:\> Test-IsProductStateOn -ProductState 393472
        False
        This example shows basic functionality
    .OUTPUTS
        Bool
    .NOTES
        This function was build to resolve the state of a Antivirus Provider registered in Security Center.
        Using this function it is possible to read which product is set to On or not.
        Other states are Off, Snoozed and Expired which can be resolved by using the enums provided in this module.
        Example: Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntivirusProduct | Where-Object {($_.productState -band [ProductFlags]::ProductState) -eq [ProductState]::Off}
        Will list all products that are disabled.
        Use Add-ProductStates to return the actual state or cast the value using the stateflag
        $prod = Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntivirusProduct
        [SignatureStatus]($prod[0].productState -band [ProductFlags]::SignatureStatus)
    #>

    [CmdletBinding()]
    param (
        # Product State contains a value (DWORD) that contains multiple bitflags and we use the productState flag (0000F000)
        [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName, HelpMessage = "The value (DWORD) containing the bitflags.")]
        [Alias("STATE")]
        [UInt32]$ProductState
    )

    try
    {
        if( $([ProductState]::On -and $($ProductState -band [ProductFlags]::ProductState) ) )
        {
            return $true
        }
        else
        {
            return $false
        }
    }
    catch
    {
        return $false
    }
}

<#
.SYNOPSIS
    Return $true if the value is found
.DESCRIPTION
    Return $true if the value is found, $false if not
.EXAMPLE
    PS C:\> Test-RegistryValue -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Value "ProductName"
    The output of the function above will return true.
.OUTPUTS
    Boolean value which corresponds to the outcome
#>

function Test-RegistryValue
{
param (

    [parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]$Path,

    [parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]$Value
)

    try
    {
        Get-ItemProperty -Path $Path -ErrorAction Stop | Select-Object -ExpandProperty $Value -ErrorAction Stop | Out-Null
        return $true
    }
    catch
    {
        return $false
    }
}