DSCResources/MSFT_IntuneCorporateDeviceIdentifier/MSFT_IntuneCorporateDeviceIdentifier.psm1

Confirm-M365DSCModuleDependency -ModuleName 'MSFT_IntuneCorporateDeviceIdentifier'

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        [ValidateSet('Yes')]
        $IsSingleInstance,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $Devices,

        [Parameter()]
        [System.String]
        [ValidateSet('Absent', 'Present')]
        $Ensure = 'Present',

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $Credential,

        [Parameter()]
        [System.String]
        $ApplicationId,

        [Parameter()]
        [System.String]
        $TenantId,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $ApplicationSecret,

        [Parameter()]
        [System.String]
        $CertificateThumbprint,

        [Parameter()]
        [Switch]
        $ManagedIdentity,

        [Parameter()]
        [System.String[]]
        $AccessTokens
    )

    try
    {
        $null = New-M365DSCConnection -Workload 'MicrosoftGraph' `
            -InboundParameters $PSBoundParameters

        #Ensure the proper dependencies are installed in the current environment.
        Confirm-M365DSCDependencies

        #region Telemetry
        $ResourceName = $MyInvocation.MyCommand.ModuleName -replace 'MSFT_', ''
        $CommandName = $MyInvocation.MyCommand
        $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName `
            -CommandName $CommandName `
            -Parameters $PSBoundParameters
        Add-M365DSCTelemetryEvent -Data $data
        #endregion

        $nullResult = $PSBoundParameters
        $nullResult.Ensure = 'Absent'
        $nullResult.Devices = @()

        # Get all imported device identities from Intune
        $uri = (Get-MSCloudLoginConnectionProfile -Workload MicrosoftGraph).ResourceUrl + 'beta/deviceManagement/importedDeviceIdentities'
        $allDevices = @()

        do
        {
            $response = Invoke-MgGraphRequest -Method GET -Uri $uri
            if ($null -ne $response.value)
            {
                $allDevices += $response.value
            }
            $uri = $response.'@odata.nextLink'
        } while ($null -ne $uri)

        if ($allDevices.Count -eq 0)
        {
            Write-Verbose -Message "No corporate device identifiers found in Intune"
            return $nullResult
        }

        Write-Verbose -Message "Found $($allDevices.Count) corporate device identifiers in Intune"

        # Convert to CIM instances
        $deviceArray = @()
        foreach ($device in $allDevices)
        {
            $deviceHash = @{
                Id                          = $device.id
                importedDeviceIdentifier    = $device.importedDeviceIdentifier
                importedDeviceIdentityType  = $device.importedDeviceIdentityType
                Description                 = $device.description
                Platform                    = if ($device.platform) { $device.platform.ToLower() } else { $null }
            }
            $deviceArray += $deviceHash
        }

        $results = @{
            IsSingleInstance      = 'Yes'
            Devices               = $deviceArray
            Ensure                = 'Present'
            Credential            = $Credential
            ApplicationId         = $ApplicationId
            TenantId              = $TenantId
            ApplicationSecret     = $ApplicationSecret
            CertificateThumbprint = $CertificateThumbprint
            ManagedIdentity       = $ManagedIdentity.IsPresent
            AccessTokens          = $AccessTokens
        }

        return $results
    }
    catch
    {
        New-M365DSCLogEntry -Message 'Error retrieving data:' `
            -Exception $_ `
            -Source $($MyInvocation.MyCommand.Source) `
            -TenantId $TenantId `
            -Credential $Credential

        return $nullResult
    }
}

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        [ValidateSet('Yes')]
        $IsSingleInstance,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $Devices,

        [Parameter()]
        [System.String]
        [ValidateSet('Absent', 'Present')]
        $Ensure = 'Present',

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $Credential,

        [Parameter()]
        [System.String]
        $ApplicationId,

        [Parameter()]
        [System.String]
        $TenantId,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $ApplicationSecret,

        [Parameter()]
        [System.String]
        $CertificateThumbprint,

        [Parameter()]
        [Switch]
        $ManagedIdentity,

        [Parameter()]
        [System.String[]]
        $AccessTokens
    )
    $null = New-M365DSCConnection -Workload 'MicrosoftGraph' `
        -InboundParameters $PSBoundParameters

    #Ensure the proper dependencies are installed in the current environment.
    Confirm-M365DSCDependencies

    #region Telemetry
    $ResourceName = $MyInvocation.MyCommand.ModuleName -replace 'MSFT_', ''
    $CommandName = $MyInvocation.MyCommand
    $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName `
        -CommandName $CommandName `
        -Parameters $PSBoundParameters
    Add-M365DSCTelemetryEvent -Data $data
    #endregion

    $desiredParameters = Remove-M365DSCAuthenticationParameter -BoundParameters $PSBoundParameters
    $desiredParameters.Remove('IsSingleInstance') | Out-Null

    # Get current state
    $currentInstance = Get-TargetResource @PSBoundParameters

    if ($Ensure -eq 'Present')
    {
        # Convert CIM instances to hashtables for comparison
        $desiredDevices = @()
        if ($null -ne $Devices)
        {
            foreach ($device in $Devices)
            {
                $desiredDevices += @{
                    importedDeviceIdentifier   = if ($device.importedDeviceIdentifier) { $device.importedDeviceIdentifier.Trim() } else { $null }
                    importedDeviceIdentityType = if ($device.importedDeviceIdentityType) { $device.importedDeviceIdentityType } else { $null }
                    description                = $device.description
                    platform                   = if ($device.platform) { $device.platform.ToLower() } else { $null }
                }
            }
        }

        $currentDevices = @()
        if ($null -ne $currentInstance.Devices)
        {
            foreach ($device in $currentInstance.Devices)
            {
                $currentDevices += @{
                    Id                         = $device.Id
                    importedDeviceIdentifier   = if ($device.importedDeviceIdentifier) { $device.importedDeviceIdentifier.Trim() } else { $null }
                    importedDeviceIdentityType = if ($device.importedDeviceIdentityType) { $device.importedDeviceIdentityType } else { $null }
                    description                = $device.description
                    platform                   = if ($device.platform) { $device.platform.ToLower() } else { $null }
                }
            }
        }

        # Find devices to ADD (in desired but not in current)
        $devicesToAdd = @()
        foreach ($desiredDevice in $desiredDevices)
        {
            $found = $false
            foreach ($currentDevice in $currentDevices)
            {
                if (Compare-DeviceIdentifier -Device1 $desiredDevice -Device2 $currentDevice)
                {
                    $found = $true
                    break
                }
            }
            if (-not $found)
            {
                $devicesToAdd += $desiredDevice
            }
        }

        # Find devices to REMOVE (in current but not in desired)
        $devicesToRemove = @()
        foreach ($currentDevice in $currentDevices)
        {
            $found = $false
            foreach ($desiredDevice in $desiredDevices)
            {
                if (Compare-DeviceIdentifier -Device1 $currentDevice -Device2 $desiredDevice)
                {
                    $found = $true
                    break
                }
            }
            if (-not $found)
            {
                $devicesToRemove += $currentDevice
            }
        }

        # Add new devices
        if ($devicesToAdd.Count -gt 0)
        {
            Write-Verbose -Message "Adding $($devicesToAdd.Count) device identifier(s) to Intune"

            $importList = @()
            foreach ($device in $devicesToAdd)
            {
                $deviceToImport = @{}

                if (-not [System.String]::IsNullOrEmpty($device.importedDeviceIdentifier))
                {
                    $deviceToImport.importedDeviceIdentifier = $device.importedDeviceIdentifier
                }
                if (-not [System.String]::IsNullOrEmpty($device.importedDeviceIdentityType))
                {
                    $deviceToImport.importedDeviceIdentityType = $device.importedDeviceIdentityType
                }
                if (-not [System.String]::IsNullOrEmpty($device.description))
                {
                    $deviceToImport.description = $device.description
                }
                if (-not [System.String]::IsNullOrEmpty($device.platform))
                {
                    $deviceToImport.platform = $device.platform
                }

                $importList += $deviceToImport
            }

            $uri = (Get-MSCloudLoginConnectionProfile -Workload MicrosoftGraph).ResourceUrl + 'beta/deviceManagement/importedDeviceIdentities/importDeviceIdentityList'
            $body = @{
                overwriteImportedDeviceIdentities = $false
                importedDeviceIdentities = $importList
            }

            try
            {
                Invoke-MgGraphRequest -Method POST -Uri $uri -Body ($body | ConvertTo-Json -Depth 10)
                Write-Verbose -Message "Successfully added $($devicesToAdd.Count) device identifier(s)"
            }
            catch
            {
                Write-Verbose -Message "Error adding device identifiers: $($_.Exception.Message)"
                throw
            }
        }

        # Remove devices not in desired state
        if ($devicesToRemove.Count -gt 0)
        {
            Write-Verbose -Message "Removing $($devicesToRemove.Count) device identifier(s) from Intune"

            foreach ($device in $devicesToRemove)
            {
                $uri = (Get-MSCloudLoginConnectionProfile -Workload MicrosoftGraph).ResourceUrl + "beta/deviceManagement/importedDeviceIdentities/$($device.Id)"

                try
                {
                    Invoke-MgGraphRequest -Method DELETE -Uri $uri
                    Write-Verbose -Message "Successfully removed device identifier with Id: $($device.Id)"
                }
                catch
                {
                    Write-Verbose -Message "Error removing device identifier with Id $($device.Id): $($_.Exception.Message)"
                    throw
                }
            }
        }

        if ($devicesToAdd.Count -eq 0 -and $devicesToRemove.Count -eq 0)
        {
            Write-Verbose -Message "No changes needed - current state matches desired state"
        }
    }
    elseif ($Ensure -eq 'Absent')
    {
        # Remove ALL identifiers
        if ($null -ne $currentInstance.Devices -and $currentInstance.Devices.Count -gt 0)
        {
            Write-Verbose -Message "Removing all $($currentInstance.Devices.Count) device identifier(s) from Intune"

            foreach ($device in $currentInstance.Devices)
            {
                $uri = (Get-MSCloudLoginConnectionProfile -Workload MicrosoftGraph).ResourceUrl + "beta/deviceManagement/importedDeviceIdentities/$($device.Id)"
                try
                {
                    Invoke-MgGraphRequest -Method DELETE -Uri $uri
                    Write-Verbose -Message "Successfully removed device identifier with Id: $($device.Id)"
                }
                catch
                {
                    Write-Verbose -Message "Error removing device identifier with Id $($device.Id): $($_.Exception.Message)"
                    throw
                }
            }
        }
        else
        {
            Write-Verbose -Message "No device identifiers to remove"
        }
    }
}

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        [ValidateSet('Yes')]
        $IsSingleInstance,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $Devices,

        [Parameter()]
        [System.String]
        [ValidateSet('Absent', 'Present')]
        $Ensure = 'Present',

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $Credential,

        [Parameter()]
        [System.String]
        $ApplicationId,

        [Parameter()]
        [System.String]
        $TenantId,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $ApplicationSecret,

        [Parameter()]
        [System.String]
        $CertificateThumbprint,

        [Parameter()]
        [Switch]
        $ManagedIdentity,

        [Parameter()]
        [System.String[]]
        $AccessTokens
    )

    #region Telemetry
    $ResourceName = $MyInvocation.MyCommand.ModuleName -replace 'MSFT_', ''
    $CommandName = $MyInvocation.MyCommand
    $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName `
        -CommandName $CommandName `
        -Parameters $PSBoundParameters
    Add-M365DSCTelemetryEvent -Data $data
    #endregion


    $result = Test-M365DSCTargetResource -DesiredValues $PSBoundParameters `
        -ResourceName $($MyInvocation.MyCommand.Source).Replace('MSFT_', '')

    return $result
}

function Export-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter()]
        [System.Management.Automation.PSCredential]
        $Credential,

        [Parameter()]
        [System.String]
        $ApplicationId,

        [Parameter()]
        [System.String]
        $TenantId,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $ApplicationSecret,

        [Parameter()]
        [System.String]
        $CertificateThumbprint,

        [Parameter()]
        [Switch]
        $ManagedIdentity,

        [Parameter()]
        [System.String[]]
        $AccessTokens
    )

    $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' `
        -InboundParameters $PSBoundParameters

    #Ensure the proper dependencies are installed in the current environment.
    Confirm-M365DSCDependencies

    #region Telemetry
    $ResourceName = $MyInvocation.MyCommand.ModuleName -replace 'MSFT_', ''
    $CommandName = $MyInvocation.MyCommand
    $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName `
        -CommandName $CommandName `
        -Parameters $PSBoundParameters
    Add-M365DSCTelemetryEvent -Data $data
    #endregion

    try
    {
        if ($null -ne $Global:M365DSCExportResourceInstancesCount)
        {
            $Global:M365DSCExportResourceInstancesCount++
        }

        $params = @{
            IsSingleInstance      = 'Yes'
            Credential            = $Credential
            ApplicationId         = $ApplicationId
            TenantId              = $TenantId
            ApplicationSecret     = $ApplicationSecret
            CertificateThumbprint = $CertificateThumbprint
            ManagedIdentity       = $ManagedIdentity.IsPresent
            AccessTokens          = $AccessTokens
        }

        $Results = Get-TargetResource @Params

        if ($Results.Ensure -eq 'Present' -and $null -ne $Results.Devices -and $Results.Devices.Count -gt 0)
        {
            Write-M365DSCHost -Message "`r`n" -DeferWrite
            Write-M365DSCHost -Message " |---[1/1] Corporate Device Identifiers ($($Results.Devices.Count) devices)" -CommitWrite

            # Handle complex type conversion for Devices array
            if ($Results.Devices)
            {
                $complexMapping = @(
                    @{
                        Name            = 'Devices'
                        CimInstanceName = 'MSFT_IntuneDeviceIdentifier'
                        IsRequired      = $False
                    }
                )

                $complexTypeStringResult = Get-M365DSCDRGComplexTypeToString `
                    -ComplexObject $Results.Devices `
                    -CIMInstanceName 'MSFT_IntuneDeviceIdentifier' `
                    -ComplexTypeMapping $complexMapping

                if (-Not [String]::IsNullOrWhiteSpace($complexTypeStringResult))
                {
                    $Results.Devices = $complexTypeStringResult
                }
                else
                {
                    $Results.Remove('Devices') | Out-Null
                }
            }

            $currentDSCBlock = Get-M365DSCExportContentForResource -ResourceName $ResourceName `
                -ConnectionMode $ConnectionMode `
                -ModulePath $PSScriptRoot `
                -Results $Results `
                -Credential $Credential `
                -NoEscape @('Devices')

            Save-M365DSCPartialExport -Content $currentDSCBlock `
                -FileName $Global:PartialExportFileName

            Write-M365DSCHost -Message $Global:M365DSCEmojiGreenCheckMark -CommitWrite
            return $currentDSCBlock
        }
        else
        {
            Write-M365DSCHost -Message $Global:M365DSCEmojiGreenCheckMark -CommitWrite
            return ''
        }
    }
    catch
    {
        Write-M365DSCHost -Message $Global:M365DSCEmojiRedX -CommitWrite

        New-M365DSCLogEntry -Message 'Error during Export:' `
            -Exception $_ `
            -Source $($MyInvocation.MyCommand.Source) `
            -TenantId $TenantId `
            -Credential $Credential

        return ''
    }
}

#region Helper Functions
function Compare-DeviceIdentifier
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Collections.Hashtable]
        $Device1,

        [Parameter(Mandatory = $true)]
        [System.Collections.Hashtable]
        $Device2
    )

    # Match on importedDeviceIdentifier and importedDeviceIdentityType if both have them
    if (-not [System.String]::IsNullOrEmpty($Device1.importedDeviceIdentifier) -and
        -not [System.String]::IsNullOrEmpty($Device2.importedDeviceIdentifier))
    {
        # Case-insensitive comparison for device identifiers
        $identifierMatch = ($Device1.importedDeviceIdentifier.ToLower() -eq $Device2.importedDeviceIdentifier.ToLower())

        # Also check type if both have it
        if (-not [System.String]::IsNullOrEmpty($Device1.importedDeviceIdentityType) -and
            -not [System.String]::IsNullOrEmpty($Device2.importedDeviceIdentityType))
        {
            return ($identifierMatch -and ($Device1.importedDeviceIdentityType -eq $Device2.importedDeviceIdentityType))
        }

        return $identifierMatch
    }

    # If we can't determine a match, consider them different
    return $false
}
#endregion

Export-ModuleMember -Function *-TargetResource