Public/New-UTCMMonitor.ps1

function New-UTCMMonitor {
    <#
    .SYNOPSIS
        Creates a new UTCM configuration monitor for automated drift detection.
 
    .DESCRIPTION
        Creates a configurationMonitor via
        POST /beta/admin/configurationManagement/configurationMonitors.
        Monitors run periodically (every 6 hours at fixed GMT times: 6 AM, 12 PM, 6 PM, 12 AM)
        and compare the current tenant configuration against the specified baseline.
 
        The baseline defines which resources and property values to monitor. When a property
        drifts from its desired value, the API records a configurationDrift that can be
        queried via Get-UTCMDrift.
 
    .PARAMETER DisplayName
        Friendly name for the monitor. Required.
 
    .PARAMETER Description
        Optional description.
 
    .PARAMETER BaselineDisplayName
        Friendly name for the baseline attached to the monitor. Default: same as monitor DisplayName.
 
    .PARAMETER BaselineDescription
        Optional description for the baseline.
 
    .PARAMETER BaselineResources
        Array of hashtables defining resources to monitor. Each hashtable must have:
          - displayName (string): friendly name for this resource instance
          - resourceType (string): UTCM resource type (e.g., microsoft.exchange.sharedmailbox)
          - properties (hashtable): key-value pairs of the desired configuration state
 
    .PARAMETER SnapshotId
        Alternative to -BaselineResources. GUID of an existing UTCM snapshot whose
        captured configuration items will be converted into the baseline. Internally
        fetches the snapshot via Get-UTCMSnapshot -IncludeItems and pipes its items
        through ConvertTo-UTCMMonitorResource, applying any of the filtering
        parameters below.
 
    .PARAMETER ResourceType
        When sourcing from -SnapshotId: optional resource-type allow-list used to
        narrow the snapshot down to just the types you want to monitor.
 
    .PARAMETER Identity
        When sourcing from -SnapshotId: optional id allow-list (matched against the
        normalized item id).
 
    .PARAMETER IncludeProperty
        When sourcing from -SnapshotId: optional property allow-list. Only these
        property names are carried into each resource's baseline properties.
 
    .PARAMETER ExcludeProperty
        When sourcing from -SnapshotId: optional property deny-list, applied after
        IncludeProperty.
 
    .PARAMETER Parameters
        Optional hashtable of key-value pairs that can be referenced in the baseline.
 
    .OUTPUTS
        PSObject representing the created configurationMonitor.
 
    .EXAMPLE
        $resources = @(
            @{
                displayName = 'TestSharedMailbox Resource'
                resourceType = 'microsoft.exchange.sharedmailbox'
                properties = @{
                    DisplayName = 'TestSharedMailbox'
                    Alias = 'testSharedMailbox'
                    Identity = 'TestSharedMailbox'
                    Ensure = 'Present'
                    PrimarySmtpAddress = 'testSharedMailbox@contoso.onmicrosoft.com'
                }
            }
        )
        New-UTCMMonitor -DisplayName 'Exchange Monitor' -BaselineResources $resources
 
    .EXAMPLE
        # Monitor an accepted domain stays present
        $resources = @(
            @{
                displayName = 'Accepted Domain'
                resourceType = 'microsoft.exchange.accepteddomain'
                properties = @{
                    Identity = 'contoso.onmicrosoft.com'
                    DomainType = 'InternalRelay'
                    Ensure = 'Present'
                }
            }
        )
        New-UTCMMonitor -DisplayName 'Domain Monitor' -Description 'Ensure domain stays present' -BaselineResources $resources
 
    .EXAMPLE
        # Build a monitor from an existing snapshot, filtered to a single resource type
        New-UTCMMonitor -DisplayName 'Shared Mailbox Monitor' `
            -SnapshotId 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' `
            -ResourceType microsoft.exchange.sharedmailbox
 
    .EXAMPLE
        # Build a monitor from a snapshot, keeping only specific properties
        New-UTCMMonitor -DisplayName 'Domain Monitor' `
            -SnapshotId $snapshotId `
            -ResourceType microsoft.exchange.accepteddomain `
            -IncludeProperty Identity,DomainType,Ensure
    #>

    [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'FromResources')]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $DisplayName,

        [string] $Description,

        [string] $BaselineDisplayName,

        [string] $BaselineDescription,

        [Parameter(Mandatory, ParameterSetName = 'FromResources')]
        [ValidateNotNullOrEmpty()]
        [hashtable[]] $BaselineResources,

        [Parameter(Mandatory, ParameterSetName = 'FromSnapshot')]
        [ValidateScript({
            if (-not (Validate-Guid $_)) { throw "SnapshotId '$_' is not a valid GUID." }
            $true
        })]
        [string] $SnapshotId,

        [Parameter(ParameterSetName = 'FromSnapshot')]
        [string[]] $ResourceType,

        [Parameter(ParameterSetName = 'FromSnapshot')]
        [string[]] $Identity,

        [Parameter(ParameterSetName = 'FromSnapshot')]
        [string[]] $IncludeProperty,

        [Parameter(ParameterSetName = 'FromSnapshot')]
        [string[]] $ExcludeProperty,

        [hashtable] $Parameters
    )

    if (Get-Command -Name Ensure-GraphConnection -ErrorAction SilentlyContinue) {
        Ensure-GraphConnection
    }

    if (-not $BaselineDisplayName) {
        $BaselineDisplayName = "$DisplayName Baseline"
    }

    # If sourcing from a snapshot, materialize BaselineResources via the converter.
    if ($PSCmdlet.ParameterSetName -eq 'FromSnapshot') {
        if (-not (Get-Command -Name ConvertTo-UTCMMonitorResource -ErrorAction SilentlyContinue)) {
            throw "ConvertTo-UTCMMonitorResource is not available; cannot build baseline from snapshot."
        }

        $convertParams = @{ SnapshotId = $SnapshotId }
        if ($ResourceType)    { $convertParams['ResourceType']    = $ResourceType }
        if ($Identity)        { $convertParams['Identity']        = $Identity }
        if ($IncludeProperty) { $convertParams['IncludeProperty'] = $IncludeProperty }
        if ($ExcludeProperty) { $convertParams['ExcludeProperty'] = $ExcludeProperty }

        $BaselineResources = @(ConvertTo-UTCMMonitorResource @convertParams)

        if (-not $BaselineResources -or $BaselineResources.Count -eq 0) {
            throw ("Snapshot '{0}' produced no resources for the supplied filters; nothing to monitor." -f $SnapshotId)
        }
    }

    # Build baseline resource objects
    $resourceObjects = foreach ($res in $BaselineResources) {
        if (-not $res.displayName -or -not $res.resourceType -or -not $res.properties) {
            throw "Each BaselineResources entry must have 'displayName', 'resourceType', and 'properties' keys."
        }
        @{
            displayName  = $res.displayName
            resourceType = $res.resourceType
            properties   = $res.properties
        }
    }

    $body = @{
        displayName = $DisplayName
        baseline    = @{
            displayName = $BaselineDisplayName
            resources   = @($resourceObjects)
        }
    }

    if ($Description) {
        $body['description'] = $Description
    }
    if ($BaselineDescription) {
        $body['baseline']['description'] = $BaselineDescription
    }
    if ($Parameters) {
        $body['parameters'] = $Parameters
    }

    if (-not $PSCmdlet.ShouldProcess($DisplayName, "Create UTCM configuration monitor")) {
        return
    }

    if (Get-Command -Name Write-Log -ErrorAction SilentlyContinue) {
        Write-Log -Message ("Creating UTCM monitor: {0} (resources={1})" -f $DisplayName, ($resourceObjects.Count)) -Color Cyan
    }

    $result = Invoke-GraphRequestWithRetry -Method 'POST' -Uri $script:ConfigurationMonitorsUri -Body $body

    if (Get-Command -Name Write-Log -ErrorAction SilentlyContinue) {
        Write-Log -Message ("Monitor created: {0} (id={1}, status={2})" -f $result.displayName, $result.id, $result.status) -Color Green
    }

    return $result
}