HybridHealthServices.ps1

# Creates a new ADHybridHealthService
# Jun 7th 2021
function New-HybridHealthService
{
<#
    .SYNOPSIS
    Creates a new ADHybridHealthService

    .DESCRIPTION
    Creates a new ADHybridHealthService

    .Parameter AccessToken
    The access token used to create ADHybridHealthServices.

    .Parameter Service
    Which kind of service to create. Can be one of: "AdFederationService","AadSyncService"
    Defaults to AdFederationService

    .Parameter DisplayName
    Display name of the service. Usually host name of the AD FS service, like sts.company.com

    .Parameter Signature
    The issuer uri of the AD FS service. Usually same as the display name, like sts.company.com

    .Parameter Disabled
    Whether the service is disabled or not. Defaults to $False

    .Parameter Health
    Health of the service. Can be one of: "Healthy","NotMonitored","Error"
    Defaults to "Healthy"

    .Example
    New-AADIntHybridHealthService -Service AdFederationService -DisplayName sts.company.com -Signature sts.company.com

    activeAlerts : 0
    additionalInformation :
    createdDate : 2021-05-05T07:13:45.0508805Z
    customNotificationEmails :
    disabled : False
    displayName : sts.company.com
    health : Healthy
    lastDisabled :
    lastUpdated : 0001-01-01T00:00:00
    monitoringConfigurationsComputed :
    monitoringConfigurationsCustomized :
    notificationEmailEnabled : True
    notificationEmailEnabledForGlobalAdmins : True
    notificationEmails :
    notificationEmailsEnabledForGlobalAdmins : False
    resolvedAlerts : 0
    serviceId : 50abc8f3-243a-4ac1-a3fb-712054d7334b
    serviceMembers :
    serviceName : AdFederationService-sts.company.com
    signature : sts.company.com
    simpleProperties :
    tenantId : 5b53828e-8e7b-42d1-a5f0-9b34bbd1844a
    type : AdFederationService
    originalDisabledState : False
    id : /providers/Microsoft.ADHybridHealthService/services/AdFederationService-sts.company.com
#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$False)]
        [String]$AccessToken,
        [Parameter(Mandatory=$False)]
        [ValidateSet("AdFederationService","AadSyncService")]
        [String]$Type = "AdFederationService",
        [Parameter(Mandatory=$True)]
        [String]$DisplayName,
        [Parameter(Mandatory=$True)]
        [String]$Signature
    )
    Process
    {
        # Get from cache if not provided
        $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://management.core.windows.net/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"

        # Set the headers
        $headers=@{
            "Authorization"          = "Bearer $AccessToken"
            "x-ms-client-request-id" = (New-Guid).ToString()
        }

        # Create the body
        $body = [ordered]@{
            "ActiveAlerts"                             = 0
            "AdditionalInformation"                    = $null
            "CreatedDate"                              = "0001-01-01T00:00:00"
            "CustomNotificationEmails"                 = $null
            "Disabled"                                 = $False
            "DisplayName"                              = $DisplayName
            "Health"                                   = "Healthy"
            "LastDisabled"                             = $null
            "LastUpdated"                              = "0001-01-01T00:00:00"
            "MonitoringConfigurationsComputed"         = $null
            "MonitoringConfigurationsCustomized"       = $null
            "NotificationEmailEnabled"                 = $null
            "NotificationEmailEnabledForGlobalAdmins"  = $null
            "NotificationEmails"                       = $null
            "NotificationEmailsEnabledForGlobalAdmins" = $false
            "ResolvedAlerts"                           = 0
            "ServiceId"                                = $null
            "ServiceMembers"                           = $null
            "ServiceName"                              = $null
            "Signature"                                = $Signature
            "SimpleProperties"                         = $null
            "TenantId"                                 = $null
            "Type"                                     = $Type
            "OriginalDisabledState"                    = $false
        }

        # Invoke the command
        $response = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://management.azure.com/providers/Microsoft.ADHybridHealthService/services?api-version=2014-01-01" -Headers $headers -Body ($body | ConvertTo-Json) -ContentType "application/json; charset=utf-8"

        # Return the service object
        $response
        
    }
}

# Lists ADHybridHealthServices
# May 26th 2021
function Get-HybridHealthServices
{
<#
    .SYNOPSIS
    Gets ADHybridHealthServices

    .DESCRIPTION
    Gets ADHybridHealthServices

    .Parameter AccessToken
    The access token used to get ADHybridHealthServices.

    .Parameter Service
    Which kind of services to return.

    .Example
    Get-AADIntHybridHealthServices -Service AdFederationService

    activeAlerts : 3
    additionalInformation :
    createdDate : 2021-05-05T07:13:45.0508805Z
    customNotificationEmails :
    disabled : False
    displayName : sts.company.com
    health : Error
    lastDisabled :
    lastUpdated : 2021-05-06T06:04:20.6537234Z
    monitoringConfigurationsComputed :
    monitoringConfigurationsCustomized :
    notificationEmailEnabled : True
    notificationEmailEnabledForGlobalAdmins : True
    notificationEmails :
    notificationEmailsEnabledForGlobalAdmins : False
    resolvedAlerts : 1
    serviceId : 50abc8f3-243a-4ac1-a3fb-712054d7334b
    serviceMembers :
    serviceName : AdFederationService-sts.company.com
    signature : sts.company.com
    simpleProperties :
    tenantId : 5b53828e-8e7b-42d1-a5f0-9b34bbd1844a
    type : AdFederationService
    originalDisabledState : False
    id : /providers/Microsoft.ADHybridHealthService/services/AdFederationService-sts.company.com

    .Example
    PS C:\>Get-AADIntHybridHealthServices -Service AdFederationService | ft serviceName

    serviceName
    -----------
    AdFederationService-sts.company.com
    AdFederationService-sts.contoso.com
#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$False)]
        [String]$AccessToken,
        [Parameter(Mandatory=$False)]
        [ValidateSet("AdFederationService","AadSyncService")]
        [String]$Service="AdFederationService"

    )
    Process
    {
        # Get from cache if not provided
        $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://management.core.windows.net/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"

        # Set the headers
        $headers=@{
            "Authorization" = "Bearer $AccessToken"
        }

        $url="https://management.azure.com/providers/Microsoft.ADHybridHealthService/services?api-version=2014-01-01"
        if($Service)
        {
            $url += "&serviceType=$Service"
        }

        # Invoke the command
        $response = Invoke-RestMethod -UseBasicParsing -Method Get -Uri $url -Headers $headers

        # Return services
        $response.value
        
    }
}

# Removes the ADHybridHealthService
# Jun 7th 2021
function Remove-HybridHealthService
{
<#
    .SYNOPSIS
    Removes existing ADHybridHealthService

    .DESCRIPTION
    Removes existing ADHybridHealthService

    .Parameter AccessToken
    The access token used to get ADHybridHealthServices.

    .Parameter ServiceName
    Name of the service to be removed

    .Example
    Remove-AADIntHybridHealthService -ServiceName AdFederationService-sts.company.com

#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$False)]
        [String]$AccessToken,
        [Parameter(Mandatory=$True)]
        [String]$ServiceName
    )
    Process
    {
        # Get from cache if not provided
        $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://management.core.windows.net/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"

        # Set the headers
        $headers=@{
            "Authorization"          = "Bearer $AccessToken"
            "x-ms-client-request-id" = (New-Guid).ToString()
        }

        # Invoke the command
        $response = Invoke-RestMethod -UseBasicParsing -Method Delete -Uri "https://management.azure.com/providers/Microsoft.ADHybridHealthService/services/$ServiceName`?api-version=2014-01-01" -Headers $headers

        # Return the service object
        $response
        
    }
}

# Get ADHybridHealthService members
# Jun 7th 2021
function Get-HybridHealthServiceMembers
{
<#
    .SYNOPSIS
    Gets ADHybridHealthService members

    .DESCRIPTION
    Gets ADHybridHealthService members

    .Parameter AccessToken
    The access token used to get ADHybridHealthService members.

    .Parameter ServiceName
    Name of the ADHybridHealthService

    .Example
    Get-AADIntHybridHealthServiceMembers -ServiceName "AdFederationService-sts.company.com"

    lastReboot : 2021-03-16T08:17:19.0912Z
    lastDisabled :
    lastUpdated : 2021-06-07T11:36:34.6667535Z
    activeAlerts : 1
    resolvedAlerts : 1
    createdDate : 0001-01-01T00:00:00
    disabled : False
    dimensions :
    additionalInformation :
    tenantId : 5b53828e-8e7b-42d1-a5f0-9b34bbd1844a
    serviceId : 50abc8f3-243a-4ac1-a3fb-712054d7334b
    serviceMemberId : bec07a23-dd4a-4c80-8c92-9b9dc089f75c
    machineId : 0cf2774f-a188-4bd3-b4b3-3a690374325d
    machineName : SERVER
    role : AdfsServer_2016
    status : Warning
    properties :
    installedQfes :
    recommendedQfes :
    monitoringConfigurationsComputed :
    monitoringConfigurationsCustomized :
    osVersion : 10.0.17763.0
    osName : Microsoft Windows Server 2019 Standard
    disabledReason : 0
    serverReportedMonitoringLevel :
    lastServerReportedMonitoringLevelChange :

    lastReboot : 0001-01-01T00:00:00
    lastDisabled :
    lastUpdated : 0001-01-01T00:00:00
    activeAlerts : 0
    resolvedAlerts : 0
    createdDate : 0001-01-01T00:00:00
    disabled : False
    dimensions :
    additionalInformation :
    tenantId : 5b53828e-8e7b-42d1-a5f0-9b34bbd1844a
    serviceId : 50abc8f3-243a-4ac1-a3fb-712054d7334b
    serviceMemberId : e4d72022-a268-4167-a964-1899b8baeaa5
    machineId : f5e349d6-67fd-4f11-b489-d98980aa6cab
    machineName : PROXY
    role : AdfsProxy_21
    status : Healthy
    properties :
    installedQfes :
    recommendedQfes :
    monitoringConfigurationsComputed :
    monitoringConfigurationsCustomized :
    osVersion :
    osName :
    disabledReason : 0
    serverReportedMonitoringLevel :
    lastServerReportedMonitoringLevelChange :

    .Example
    Get-AADIntHybridHealthServiceMembers -ServiceName "AdFederationService-sts.company.com" | ft machineName,serviceMemberId

    machineName serviceMemberId
    ----------- ---------------
    SERVER bec07a23-dd4a-4c80-8c92-9b9dc089f75c
    PROXY e4d72022-a268-4167-a964-1899b8baeaa5

#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$False)]
        [String]$AccessToken,
        [Parameter(Mandatory=$True)]
        [String]$ServiceName
    )
    Process
    {
        # Get from cache if not provided
        $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://management.core.windows.net/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"

        # Set the headers
        $headers=@{
            "Authorization" = "Bearer $AccessToken"
            "x-ms-client-request-id" = (New-Guid).ToString()
        }
        
        # Invoke the command
        $response = Invoke-RestMethod -UseBasicParsing -Uri "https://management.azure.com/providers/Microsoft.ADHybridHealthService/services/$ServiceName/servicemembers?api-version=2014-01-01" -Headers $headers -Body ($Body | ConvertTo-Json) -ContentType "application/json; charset=utf-8"

        # Return the service members
        $response.value
    }
}

# Create a new ADHybridHealthService members
# May 26th 2021
function New-HybridHealthServiceMember
{
<#
    .SYNOPSIS
    Adds a new ADHybridHealthService member

    .DESCRIPTION
    Adds a new ADHybridHealthService member

    .Parameter AccessToken
    The access token used to get ADHybridHealthService members.

    .Parameter ServiceName
    Name of the ADHybridHealthService

    .Example
    New-AADIntHybridHealthServiceMember -ServiceName AdFederationService-sts.company.com -MachineName "MyServer"

    lastReboot : 0001-01-01T00:00:00Z
    lastDisabled :
    lastUpdated : 0001-01-01T00:00:00
    activeAlerts : 0
    resolvedAlerts : 0
    createdDate : 2021-05-06T07:15:50.0087136Z
    disabled : False
    dimensions :
    additionalInformation :
    tenantId : 5b53828e-8e7b-42d1-a5f0-9b34bbd1844a
    serviceId : 50abc8f3-243a-4ac1-a3fb-712054d7334b
    serviceMemberId : 0fce7ce0-81a0-4bf7-87fb-fc787dfe13c2
    machineId : e9f8357d-8a25-4cef-8c6b-f0b3c916ead5
    machineName : MyServer
    role :
    status : Healthy
    properties :
    installedQfes :
    recommendedQfes :
    monitoringConfigurationsComputed :
    monitoringConfigurationsCustomized :
    osVersion :
    osName :
    disabledReason : 0
    serverReportedMonitoringLevel :
    lastServerReportedMonitoringLevelChange :
#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$False)]
        [String]$AccessToken,
        [Parameter(Mandatory=$True)]
        [String]$ServiceName,
        [Parameter(Mandatory=$False)]
        [guid]$MachineId=(New-Guid),
        [Parameter(Mandatory=$True)]
        [String]$MachineName,
        [Parameter(Mandatory=$False)]
        [ValidateSet("AdfsServer_2x","AdfsProxy_2x","AdfsServer_21","AdfsProxy_21","AdfsServer_30","AdfsProxy_30","AdfsServer_2016","AdfsProxy_2016")]
        [String]$MachineRole="AdfsServer_2016"
    )
    Process
    {
        # Get from cache if not provided
        $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://management.core.windows.net/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"

        # Set the headers
        $headers=@{
            "Authorization" = "Bearer $AccessToken"
            "x-ms-client-request-id" = (New-Guid).ToString()
        }

        # Create the body
        $body= [ordered]@{
            "LastReboot"                             = "0001-01-01T00:00:00"
            "LastDisabled"                           = "0001-01-01T00:00:00"
            "LastUpdated"                            = "0001-01-01T00:00:00"
            "ActiveAlerts"                           = 0
            "ResolvedAlerts"                         = 0
            "CreatedDate"                            = "0001-01-01T00:00:00"
            "Disabled"                               = $False
            "Dimensions"                             = $null
            "AdditionalInformation"                  = $null
            "TenantId"                               = "00000000-0000-0000-0000-000000000000"
            "ServiceId"                              = "00000000-0000-0000-0000-000000000000"
            "ServiceMemberId"                        = "00000000-0000-0000-0000-000000000000"
            "MachineId"                              = $MachineId.ToString()
            "MachineName"                            = $MachineName
            "Role"                                   = $MachineRole
            "Status"                                 = $Status
            "Properties"                             = $Null
            "InstalledQfes"                          = $Null
            "RecommendedQfes"                        = $Null
            "MonitoringConfigurationsComputed"       = $Null
            "MonitoringConfigurationsCustomized"     = $Null
            "OsVersion"                              = $Null
            "OsName"                                 = $Null
            "DisabledReason"                         = 0
            "ServerReportedMonitoringLevel"          = $Null
            "LastServerReportedMonitoringLevelChange"= "0001-01-01T00:00:00"
        }

        # Invoke the command
        $response = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://management.azure.com/providers/Microsoft.ADHybridHealthService/services/$ServiceName/servicemembers?api-version=2014-01-01" -Headers $headers -Body ($Body | ConvertTo-Json) -ContentType "application/json; charset=utf-8"

        # Return the service object
        $response
    }
}

# Remove ADHybridHealthService members
# Jun 14th 2021
function Remove-HybridHealthServiceMember
{
<#
    .SYNOPSIS
    Removes ADHybridHealthService member

    .DESCRIPTION
    Removes ADHybridHealthService member

    .Parameter AccessToken
    The access token used to get ADHybridHealthService members.

    .Parameter ServiceName
    Name of the ADHybridHealthService

    .Parameter ServiceMemberId
    Id of the ADHybridHealthService member to be removed

    .Example
    Remove-AADIntHybridHealthServiceMember -ServiceName AdFederationService-sts.company.com -ServiceMemberId 329485ce-9b5b-4652-ba72-acc41a455e92
#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$False)]
        [String]$AccessToken,
        [Parameter(Mandatory=$True)]
        [String]$ServiceName,
        [Parameter(Mandatory=$True)]
        [guid]$ServiceMemberId
    )
    Process
    {
        # Get from cache if not provided
        $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://management.core.windows.net/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"

        # Set the headers
        $headers=@{
            "Authorization" = "Bearer $AccessToken"
            "x-ms-client-request-id" = (New-Guid).ToString()
        }

        # Invoke the command
        $response = Invoke-RestMethod -UseBasicParsing -Method Delete -Uri "https://management.azure.com/providers/Microsoft.ADHybridHealthService/services/$ServiceName/servicemembers/$ServiceMemberId`?confirm=false&api-version=2014-01-01" -Headers $headers

        # Return the service object
        $response
    }
}

# Gets ADHybridHealthService monitoring policies
# May 29th 2021
function Get-HybridHealthServiceMonitoringPolicies
{
<#
    .SYNOPSIS
    Gets ADHybridHealthService monitoring policies.

    .DESCRIPTION
    Gets ADHybridHealthService monitoring policies.

    .Parameter AccessToken
    The access token used to get ADHybridHealthService monitoring policies

    .Example
    Get-AADIntHybridHealthServiceMonitoringPolicies -AccessToken $at

    serviceType : AdFederationService
    serviceId : 74b6a260-67a3-43ac-922f-ec7afe19649c
    serviceMemberId : 52f7c09f-e6a4-41ff-b328-bb6a182e1aca
    monitoringConfigurations : {@{key=AadPremium; value=True}, @{key=MonitoringLevel; value=Full}}
    propertiesExtractorClassName : Microsoft.Identity.Health.Adfs.DataAccess.DataManager, Microsoft.Identity.Health.Adfs.DataAccess
    dimensionTableEntityClassNameList :
    roleType : AdfsServer_2016
    moduleConfigurations : {@{agentService=ConnectorAgent; moduleName=adfs; properties=}, @{agentService=ConnectorAgent; moduleName=PowerShellCmdletMonitor; properties=}}

    serviceType : AadSyncService
    serviceId : 4ce7a4dd-0269-4ae1-a92c-88f381f11a33
    serviceMemberId : fa657e9b-b609-470c-aa6a-9922d9f37e49
    monitoringConfigurations : {@{key=MonitoringLevel; value=Off}, @{key=StagingMode; value=False}, @{key=ConfigurationUploadInterval; value=240},
                                        @{key=RunProfileResultUploadInterval; value=30}...}
    propertiesExtractorClassName : Microsoft.Identity.Health.AadSync.DataAccess.DataManager, Microsoft.Identity.Health.AadSync.DataAccess
    dimensionTableEntityClassNameList :
    roleType : AadSync_AadConnectSync_1.0
    moduleConfigurations : {@{agentService=ConnectorAgent; moduleName=aadsync; properties=}}
#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [String]$AccessToken
    )
    Process
    {
        $headers = @{
            "Authorization" = "Bearer $AccessToken"
            "x-ms-client-request-id" = (New-Guid).ToString()
        }

        # Invoke the command
        $response = Invoke-RestMethod -UseBasicParsing -Method Get -Uri "https://s1.adhybridhealth.azure.com/providers/Microsoft.ADHybridHealthService/monitoringpolicies" -Headers $headers

        # Return upload key
        $response
    }
}

# Send the ADHybridHealthService events to Azure
# May 26th 2021
function Send-HybridHealthServiceEvents
{
<#
    .SYNOPSIS
    Sends the given AD FS audit events to Azure.

    .DESCRIPTION
    Sends the given AD FS audit events to Azure using ADHybridHealthService protocols.

    .Parameter TenantId
    Tenant ID

    .Parameter ServiceID
    ServiceID

    .Parameter MachineId
    Machine ID of the computer running the ADHybridHealthService.

    .Parameter Events
    An array of event objects.

    .Example
    PS C:\>Get-AADIntHybridHealthServiceMembers -ServiceName "AdFederationService-sts.company.com" | ft machineId,serviceId,tenantId

    machineName machineId serviceId tenantId
    ----------- --------- --------- --------
    SERVER 0cf2774f-a188-4bd3-b4b3-3a690374325d a0fae99d-083e-451c-9965-cc7a5851e4a8 b00133a8-b4e1-4c69-91d1-c0945e3e83c4
    PROXY f5e349d6-67fd-4f11-b489-d98980aa6cab a0fae99d-083e-451c-9965-cc7a5851e4a8 b00133a8-b4e1-4c69-91d1-c0945e3e83c4

    PS C:\>$agentKey = Get-Content "b00133a8-b4e1-4c69-91d1-c0945e3e83c4_f5e349d6-67fd-4f11-b489-d98980aa6cab_SERVER.txt"
    PS C:\>$events = @()
    PS C:\>$events += (New-AADIntHybridHealtServiceEvent -Server "Server" -UPN "user@company.com" -IPAddress "192.168.0.2")

    PS C:\>Send-AADIntHybridHealthServiceEvents -AgentKey $agentKey -TenantId "b00133a8-b4e1-4c69-91d1-c0945e3e83c4" -MachineId "f5e349d6-67fd-4f11-b489-d98980aa6cab" -ServiceId "a0fae99d-083e-451c-9965-cc7a5851e4a8" -Events $events

    .Example
    PS C:\>$events = @()
    PS C:\>$events += (New-AADIntHybridHealtServiceEvent -Server "Server" -UPN "user@company.com" -IPAddress "192.168.0.2")
    PS C:\>$agentInfo = Get-AADIntHybridHealthServiceAgentInfo
    PS C:\>Send-AADIntHybridHealthServiceEvents -AgentInfo $agentInfo -Events $events
#>

    [cmdletbinding()]
    Param(
        [Parameter(ParameterSetName='Normal'   ,Mandatory=$True)]
        [String]$AgentKey,
        [Parameter(ParameterSetName='Normal'   ,Mandatory=$True)]
        [guid]$MachineId,
        [Parameter(ParameterSetName='Normal'   ,Mandatory=$True)]
        [guid]$TenantId,
        [Parameter(ParameterSetName='Normal'   ,Mandatory=$True)]
        [guid]$ServiceId,
        [Parameter(ParameterSetName='Normal'   ,Mandatory=$True)]
        [Parameter(ParameterSetName='AgentInfo',Mandatory=$True)]
        [System.Array]$Events,
        [Parameter(ParameterSetName='AgentInfo',Mandatory=$True)]
        [psobject]$AgentInfo
    )
    Process
    {
        if($AgentInfo)
        {
            $AgentKey  = $AgentInfo.AgentKey
            $TenantId  = $AgentInfo.TenantId
            $MachineId = $AgentInfo.MachineId
            $ServiceId = $AgentInfo.ServiceId
        }

        # Get the service access token and the needed keys
        $serviceAccessToken = Get-HybridHealthServiceAccessToken          -AgentKey $AgentKey -MachineId $MachineId -TenantId $TenantId
        $BlobKey            = Get-HybridHealthServiceBlobUploadKey        -AccessToken $serviceAccessToken -ServiceId $ServiceId
        $EventPublisherKey  = Get-HybridHealthServiceEventHubPublisherKey -AccessToken $serviceAccessToken -ServiceId $ServiceId

        # Convert the events to json and compress
        $content = ConvertTo-Json -InputObject $Events
        $encContent = Get-CompressedByteArray -byteArray ([text.encoding]::UTF8.GetBytes($content))
        
        # Calculate MD5 for the compressed content
        $md5     = [System.Security.Cryptography.MD5]::Create()
        $bodyMD5 = $md5.ComputeHash($encContent)

        # Construct headers for uploading the blob
        $id      = (New-Guid).ToString()
        $headers = @{
            "User-Agent" =             "Azure-Storage/8.2.0 (.NET CLR 4.0.30319.42000; Win32NT 10.0.17763.0)"
            "x-ms-version" =           "2017-04-17"
            "Content-MD5" =            Convert-ByteArrayToB64 -Bytes $bodyMD5
            "x-ms-blob-type" =         "BlockBlob"
            "x-ms-client-request-id" = $id
        }
        
        # Construct the url
        $BlobUrl  = $BlobKey.Replace("?","/$($id).json?")
        $BlobUrl += "&api-version=2017-04-17"

        # Send the blob to Azure
        try
        {
            $response = Invoke-RestMethod -UseBasicParsing -Method Put -Uri $BlobUrl -Headers $headers -Body ([byte[]]$encContent) 
        }
        catch
        {
            return
        }

        #
        # Create the HMAC signature for the servicebus message (this is funny)
        #

        # First, an SHA512 hash is calculated from the AgentKey.
        # Agent key is a B64 string of the binary key, but the hash is calculated from the string.
        # The hash is converted to hex string.
        [System.Security.Cryptography.SHA512] $sha = [System.Security.Cryptography.SHA512]::Create()
        $bKey = Convert-ByteArrayToHex -Bytes $sha.ComputeHash([text.encoding]::ASCII.getBytes($AgentKey))

        # Second, the signing key is derived by calculating HMACSHA512 by converting the hex array to binary by decoding it as B64 ???!?
        $cKey = Convert-B64ToByteArray -B64 $bKey.ToUpper()
        $hmac = [System.Security.Cryptography.HMACSHA512]::new($cKey)

        # Get elements needed for the signature
        $BlobUrl = $BlobUrl.Split("?")[0]
        $signingTime = Get-Date
        $dateString = $signingTime.ToUniversalTime().ToString("s", [cultureinfo]::InvariantCulture)
        
        # Form the string to be signed and calculate the signature.
        $stringToSign="$tenantId,$serviceId,$machineId,Adfs-UsageMetrics,$BlobUrl,$dateString"
        $HMACSignature = Convert-ByteArrayToB64 -Bytes $hmac.ComputeHash([text.encoding]::Unicode.GetBytes($stringToSign))
        
        # Send the signature to Azure via service bus
        Send-ADFSServiceBusMessage -EventHubPublisherKey $EventPublisherKey -BlobAbsoluteUri $BlobUrl -TenantId $TenantId -MachineId $MachineId -ServiceId $ServiceId -SigningTime $signingTime -HMACSignature $HMACSignature
    }
}

# Registers a new HybridHealthServiceAgent
# Jun 7th 2021
function Register-HybridHealthServiceAgent
{
<#
    .SYNOPSIS
    Registers a new ADHybridHealthService agent to the given service.

    .DESCRIPTION
    Registers a new ADHybridHealthService agent to the given service.
    Saves the agent info and client certificates to the current directory.
    Files are named: <ServiceName>_<TenantId>_<ServiceMemberId>_<MachineName>.xxx where xxx is json for Agent info and pfx for the certificate.

    .Example
    PS C:\>Get-AADIntAccessTokenForAzureCoreManagement -SaveToCache

    PS C:\>Get-AADIntHybridHealthServices -Service AdFederationService | ft serviceName

    serviceName
    -----------
    AdFederationService-sts.company.com
    AdFederationService-sts.contoso.com

    PS C:\>Register-AADIntHybridHealthServiceAgent -ServiceName "AdFederationService-sts.company.com" -MachineName "SERVER2" -MachineRole AdfsProxy_2016

    Agent info saved to "AdFederationService-sts.company.com_0a959715-0d39-4409-bcc9-2c6ff5aa7a37_f5e349d6-67fd-4f11-b489-d98980aa6cab_SERVER2.json"
    Client sertificate saved to "AdFederationService-sts.company.com_0a959715-0d39-4409-bcc9-2c6ff5aa7a37_f5e349d6-67fd-4f11-b489-d98980aa6cab_SERVER2.pfx"
#>


    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$False)]
        [String]$AccessToken,
        [Parameter(Mandatory=$True)]
        [String]$ServiceName,
        [Parameter(Mandatory=$True)]
        [string]$MachineName,
        [Parameter(Mandatory=$False)]
        [ValidateSet("AdfsServer_2x","AdfsProxy_2x","AdfsServer_21","AdfsProxy_21","AdfsServer_30","AdfsProxy_30","AdfsServer_2016","AdfsProxy_2016")]
        [String]$MachineRole="AdfsServer_2016",
        [ValidateSet("Healthy","NotMonitored")]
        [String]$Status = "Healthy"
     )
     Process
     {
        # Get from cache if not provided
        $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://management.core.windows.net/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"

        # Generate machine id
        $MachineId = New-Guid

        # Extract the tenant id from the
        [guid]$TenantId = (Read-Accesstoken -AccessToken $AccessToken).tid

        # Add new service member
        $serviceMember = New-HybridHealthServiceMember -AccessToken $AccessToken -ServiceName $ServiceName -MachineId $MachineId -MachineName $MachineName -MachineRole $MachineRole
        Write-Verbose "Added new service member:"
        Write-Verbose $serviceMember

        # Get the agent credentials
        $agentCredentials  = Get-HybridHealthServiceMemberCredentials -AccessToken $AccessToken -ServiceName $ServiceName -ServiceMemberId $serviceMember.serviceMemberId
        $tenantCertificate = $agentCredentials.'tenant.cert'
        Write-Verbose "Received a new tenant certificate: $($tenantCertificate.Subject)"
        Write-Verbose "AgentKey: $($agentCredentials.AgentKey)"

        # Invoke the request to get the client certificate
        Write-Verbose "Registering the agent using tenant certificate."
        [xml]$response = Invoke-RestMethod -UseBasicParsing -Uri "https://policykeyservice.dc.ad.msft.net/clientregistrationmanager.svc/ClientRegistration/$($TenantId.toString())/$MachineName/$($MachineId.toString())" -Certificate $TenantCertificate

        # Strip CRLF and convert to byte array
        $bCert = Convert-B64ToByteArray -B64 $response.AgentSetupConfiguration.ClientCertificate.Replace("`r`n","")
        $agentCert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new([byte[]]$bCert)
        Write-Verbose "Received a new agent certificate: $($agentCert.Subject)"
        
        $agentInfo=[ordered]@{
            "AgentKey"        = $agentCredentials.AgentKey
            "TenantId"        = $TenantId 
            "ServiceId"       = $serviceMember.serviceId
            "ServiceMemberId" = $serviceMember.serviceMemberId
            "MachineId"       = $MachineId
            "Server"          = $MachineName
        }

        # Save agent info and certificates to disk
        $fileName = "$($ServiceName)_$($TenantId.toString())_$($MachineId.toString())_$MachineName"
        $bCert | Set-Content "$fileName.pfx" -Encoding Byte
        $agentInfo | ConvertTo-Json | Set-Content "$fileName.json" -Encoding UTF8
        
        Write-Host "Agent info saved to ""$fileName.json"""
        Write-Host "Client sertificate saved to ""$fileName.pfx"""
     }
}