Modules/M365DSCConnection.psm1
|
[hashtable]$Script:M365DSCTelemetryConnectionToGraphParams = @{} [hashtable]$Script:M365DSCConnectionFailures = @{} <# .SYNOPSIS Gets all resources that support the specified authentication method and determines the most secure authentication method supported by the resource. .DESCRIPTION This function gets all resources that support the specified authentication method and determines the most secure authentication method supported by the resource. .PARAMETER AuthenticationMethod Specifies the authentication method to check for. Valid values are: 'ApplicationWithSecret', 'CertificateThumbprint', 'CertificatePath', 'Credentials', 'CredentialsWithTenantId', 'CredentialsWithApplicationId', 'ManagedIdentity', 'AccessTokens'. If not specified, every authentication method is evaluated. .PARAMETER Resources Specifies the resources to check. If not specified, all resources in the schema cache are checked. .FUNCTIONALITY Internal #> function Get-M365DSCComponentsWithMostSecureAuthenticationType { [CmdletBinding()] [OutputType([System.Collections.Generic.List[System.Collections.Hashtable]])] param ( [Parameter()] [System.String[]] [ValidateSet('ApplicationWithSecret', 'CertificateThumbprint', 'CertificatePath', 'Credentials', 'CredentialsWithTenantId', 'CredentialsWithApplicationId', 'ManagedIdentity', 'AccessTokens')] $AuthenticationMethod, [Parameter()] [System.String[]] $Resources ) Initialize-M365DSCDllLoader -ErrorAction Stop Initialize-M365DSCSchemaCache -ErrorAction Stop $propertyNames = Get-M365DSCResourcePropertyNameMap -Resources $Resources if ($propertyNames.Count -eq 0) { throw 'The schema cache does not contain any of the requested resources. Run Utilities/New-M365DSCDscSchemaCache.ps1 to regenerate SchemaDefinition.json.' } $requestedResources = $Resources if ($null -eq $requestedResources -or $requestedResources.Count -eq 0) { $requestedResources = [System.String[]] $propertyNames.Keys } $requestedMethods = $AuthenticationMethod if ($null -eq $requestedMethods -or $requestedMethods.Count -eq 0) { $requestedMethods = [System.String[]] $MyInvocation.MyCommand.Parameters['AuthenticationMethod'].Attributes.Where( { $_ -is [System.Management.Automation.ValidateSetAttribute] }).ValidValues } return [Microsoft365DSC.Connection.ConnectionHelper]::GetComponentsWithMostSecureAuthenticationType( [System.Collections.IDictionary]$propertyNames, $requestedMethods, $requestedResources ) } <# .SYNOPSIS Gets the names of the resources exported by the Microsoft365DSC module manifest. .DESCRIPTION Reads DscResourcesToExport from the module manifest once and caches the result for the lifetime of the module. .FUNCTIONALITY Internal .OUTPUTS System.String[] #> function Get-M365DSCExportedResourceName { [CmdletBinding()] [OutputType([System.String[]])] param() if ($null -eq $Script:M365DSCExportedResourceNames) { $manifestPath = (Resolve-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Microsoft365DSC.psd1')).Path $Script:M365DSCExportedResourceNames = [System.String[]]@((Import-PowerShellDataFile -Path $manifestPath).DscResourcesToExport) } return $Script:M365DSCExportedResourceNames } <# .SYNOPSIS Builds a map of resource name to DSC property names from the loaded schema cache. .DESCRIPTION Reads the schema held by the Microsoft365DSC cache once and returns the property names of the requested resources. Returns an empty map when the schema is not loaded. .PARAMETER Resources Specifies the resource names without the MSFT_ prefix. When omitted, every class in the schema cache is returned. .OUTPUTS System.Collections.Hashtable #> function Get-M365DSCResourcePropertyNameMap { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter()] [System.String[]] $Resources ) $map = [System.Collections.Hashtable]::new([System.StringComparer]::OrdinalIgnoreCase) if (-not [Microsoft365DSC.Cache.CacheManager]::IsSchemaLoaded) { return $map } if ($null -eq $Resources -or $Resources.Count -eq 0) { $Resources = Get-M365DSCExportedResourceName } $classes = [System.Collections.Generic.Dictionary[System.String, System.Object]]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($entry in [Microsoft365DSC.Cache.CacheManager]::Schema) { $classes[[System.String]$entry['ClassName']] = $entry } foreach ($resource in $Resources) { $definition = $null if (-not $classes.TryGetValue("MSFT_$resource", [ref] $definition)) { continue } $map[$resource] = [System.String[]]@(foreach ($parameter in $definition['Parameters']) { $parameter['Name'] }) } return $map } <# .SYNOPSIS Creates a new connection to the specified M365 workload. .DESCRIPTION This function creates a new connection to the specified M365 workload .PARAMETER Workload Specifies the M365 workload to connect to. Valid values are: 'AdminAPI', 'Azure', 'AzureDevOPS', 'DefenderForEndpoint', 'EngageHub', 'ExchangeOnline', 'Fabric', 'Licensing', 'SecurityComplianceCenter', 'PnP', 'PowerPlatforms', 'PowerPlatformREST', 'MicrosoftTeams', 'MicrosoftGraph', 'SharePointOnlineREST', 'Tasks'. .PARAMETER InboundParameters Specifies a hashtable of parameters to use for the connection. The keys and values in the hashtable should match the parameters of the Connect-M365Tenant function. .PARAMETER Url Specifies the URL to use for the connection. This parameter is optional and can be used to override the default URL for the specified workload. .PARAMETER EnableSearchOnlySession Specifies whether to enable a search-only session for the connection. This parameter is optional and can be used to limit the connection to read-only operations for the SecurityComplianceCenter workload. .FUNCTIONALITY Internal #> function New-M365DSCConnection { param ( [Parameter(Mandatory = $true)] [ValidateSet('AdminAPI', 'Azure', 'AzureDevOPS', 'DefenderForEndpoint', 'EngageHub', 'ExchangeOnline', 'Fabric', 'Licensing', ` 'SecurityComplianceCenter', 'PnP', 'PowerPlatforms', 'PowerPlatformREST', ` 'MicrosoftTeams', 'MicrosoftGraph', 'SharePointOnlineREST', 'Tasks')] [System.String] $Workload, [Parameter(Mandatory = $true)] [ValidateScript({ if ($null -ne $_.Credential) { $isValid = $_.Credential.Username -match '.onmicrosoft.' -or $_.Credential.Username -match '.onsovcloud.' if ($isValid) { return $true } else { Write-Warning -Message 'We recommend providing the username in the format of <tenant>.onmicrosoft.* (or <tenant>.onsovcloud.* for sovereign tenants) for the Credential property.' } } if ($null -ne $_.TenantId) { $isValid = [System.Guid]::TryParse($_.TenantId, [ref][System.Guid]::Empty) if ($isValid) { throw 'Please provide the tenant name (e.g., contoso.onmicrosoft.com or contoso.onsovcloud.com for sovereign tenants) for TenantId instead of its GUID.' } $isValid = $_.TenantId -match '.onmicrosoft.' -or $_.TenantId -match '.onsovcloud.' if ($isValid) { return $true } else { Write-Warning -Message 'We recommend providing the tenant name in format <tenant>.onmicrosoft.* (or <tenant>.onsovcloud.* for sovereign tenants) for TenantId.' } } return $true })] [System.Collections.Hashtable] $InboundParameters, [Parameter()] [System.String] $Url, [Parameter()] [switch] $EnableSearchOnlySession ) if (-not (Test-IsM365DSCRequiredModulesLoaded)) { $requiredModules = Get-M365DSCRequiredModules foreach ($requiredModule in $requiredModules) { Write-Verbose -Message "Ensuring required module '$requiredModule' is loaded." Confirm-M365DSCLoadedModule -ModuleName $requiredModule } Set-M365DSCRequiredModulesLoaded -Value $true } Write-Verbose -Message "Attempting connection to {$Workload} with:" Write-Verbose -Message "$(Set-M365DSCAuthenticationParameterMask -BoundParameters $InboundParameters.Clone() | Out-String)" #region Telemetry $data = [System.Collections.Generic.Dictionary[[System.String], [System.Object]]]::new() $data.Add('Source', 'M365DSCUtil') $data.Add('Workload', $Workload) $Script:M365DSCTelemetryConnectionToGraphParams = @{} # Keep track of workloads we already connected so that we don't send additional Telemetry events. if ($null -eq $Script:M365ConnectedToWorkloads) { Write-Verbose -Message 'Initializing the Connected To Workloads List.' $Script:M365ConnectedToWorkloads = @() } # Convert ApplicationSecret from SecureString to plain string for MSCloudLoginAssistant if (-not [System.String]::IsNullOrEmpty($InboundParameters.ApplicationSecret)) { if ($InboundParameters.ApplicationSecret -is [System.Management.Automation.PSCredential]) { $InboundParameters.ApplicationSecret = ConvertFrom-SecureString -SecureString $InboundParameters.ApplicationSecret.Password -AsPlainText } } #region Validation if (-not [System.String]::IsNullOrEmpty($InboundParameters.Credential) -and ` -not [System.String]::IsNullOrEmpty($InboundParameters.CertificateThumbprint)) { $message = 'Both Authentication methods are attempted' Write-Verbose -Message $message $data.Add('Exception', $message) $errorText = "You can't specify both the Credential and CertificateThumbprint" $data.Add('CustomMessage', $errorText) Add-M365DSCTelemetryEvent -Type 'Error' -Data $data throw $errorText } if ([System.String]::IsNullOrEmpty($InboundParameters.Credential) -and ` [System.String]::IsNullOrEmpty($InboundParameters.ApplicationId) -and ` [System.String]::IsNullOrEmpty($InboundParameters.TenantId) -and ` [System.String]::IsNullOrEmpty($InboundParameters.CertificateThumbprint) -and ` -not $InboundParameters.ManagedIdentity -and ` [System.String]::IsNullOrEmpty($InboundParameters.AccessTokens)) { $message = 'No Authentication method was provided' Write-Verbose -Message $message $message += "`r`nProvided Keys --> $($InboundParameters.Keys)" $data.Add('Exception', $message) $errorText = 'You must specify either the Credential or ApplicationId, TenantId and CertificateThumbprint parameters.' $data.Add('CustomMessage', $errorText) Add-M365DSCTelemetryEvent -Type 'Error' -Data $data throw $errorText } #endregion Validation # Determine connection mode using the shared helper. $connectionMode = Get-M365DSCAuthenticationMode -Parameters $InboundParameters if ($connectionMode -eq 'Interactive') { throw 'Could not determine authentication method' } Write-Verbose -Message "Connecting via $connectionMode" $failureKey = "$Workload-$connectionMode" if ($Global:M365DSCExportInProgress -and $Script:M365DSCConnectionFailures.ContainsKey($failureKey)) { throw "Connection to $Workload failed earlier in this session: $($Script:M365DSCConnectionFailures[$failureKey]) Skipping." } #region Build Connect-M365Tenant splat $connectParams = @{ Workload = $Workload EnableSearchOnlySession = $EnableSearchOnlySession.IsPresent } if (-not [System.String]::IsNullOrEmpty($Url)) { $connectParams.Url = $Url } if ($Workload -eq 'Azure' -and -not [System.String]::IsNullOrEmpty($InboundParameters.SubscriptionId)) { $connectParams.SubscriptionId = $InboundParameters.SubscriptionId } switch ($connectionMode) { 'Credentials' { $connectParams.Credential = $InboundParameters.Credential } 'CredentialsWithApplicationId' { $connectParams.ApplicationId = $InboundParameters.ApplicationId $connectParams.Credential = $InboundParameters.Credential } 'CredentialsWithTenantId' { $connectParams.TenantId = $InboundParameters.TenantId $connectParams.Credential = $InboundParameters.Credential } 'ServicePrincipalWithPath' { $connectParams.ApplicationId = $InboundParameters.ApplicationId $connectParams.TenantId = $InboundParameters.TenantId $connectParams.CertificatePassword = $InboundParameters.CertificatePassword.Password $connectParams.CertificatePath = $InboundParameters.CertificatePath } 'ServicePrincipalWithSecret' { $connectParams.ApplicationId = $InboundParameters.ApplicationId $connectParams.TenantId = $InboundParameters.TenantId $connectParams.ApplicationSecret = $InboundParameters.ApplicationSecret } 'ServicePrincipalWithThumbprint' { $connectParams.ApplicationId = $InboundParameters.ApplicationId $connectParams.TenantId = $InboundParameters.TenantId $connectParams.CertificateThumbprint = $InboundParameters.CertificateThumbprint } 'ManagedIdentity' { $connectParams.Identity = $true $connectParams.TenantId = $InboundParameters.TenantId } 'AccessTokens' { $connectParams.AccessTokens = $InboundParameters.AccessTokens $connectParams.TenantId = $InboundParameters.TenantId } } #endregion try { Connect-M365Tenant @connectParams } catch { Register-M365DSCConnectionFailure -FailureKey $failureKey -Message $_.Exception.Message throw } #region Update telemetry cache $telemetryCacheKeys = switch ($connectionMode) { 'Credentials' { @('Credential') } 'CredentialsWithApplicationId' { @('Credential', 'ApplicationId') } 'CredentialsWithTenantId' { @('Credential', 'TenantId') } 'ServicePrincipalWithPath' { @('ApplicationId', 'TenantId', 'CertificatePath') } 'ServicePrincipalWithSecret' { @('ApplicationId', 'TenantId', 'ApplicationSecret') } 'ServicePrincipalWithThumbprint' { @('ApplicationId', 'TenantId', 'CertificateThumbprint') } 'ManagedIdentity' { @('TenantId') } 'AccessTokens' { @('AccessTokens', 'TenantId') } } foreach ($key in $telemetryCacheKeys) { if (-not $Script:M365DSCTelemetryConnectionToGraphParams.ContainsKey($key) -and $null -ne $InboundParameters[$key]) { $Script:M365DSCTelemetryConnectionToGraphParams.Add($key, $InboundParameters[$key]) } } # Handle special telemetry cache values not directly from InboundParameters. if ($connectionMode -eq 'ManagedIdentity' -and -not $Script:M365DSCTelemetryConnectionToGraphParams.ContainsKey('Identity')) { $Script:M365DSCTelemetryConnectionToGraphParams.Add('Identity', $true) } if ($ConnectionMode -eq 'ServicePrincipalWithSecret' -and -not $Script:M365DSCTelemetryConnectionToGraphParams.ContainsKey('ApplicationSecret')) { $Script:M365DSCTelemetryConnectionToGraphParams.Add('ApplicationSecret', $InboundParameters.ApplicationSecret.Password) } if ($connectionMode -eq 'ServicePrincipalWithPath' -and -not $Script:M365DSCTelemetryConnectionToGraphParams.ContainsKey('CertificatePassword')) { $Script:M365DSCTelemetryConnectionToGraphParams.Add('CertificatePassword', $InboundParameters.CertificatePassword.Password) } #endregion #region Emit connection telemetry # The Credentials mode uses 'Credential' (no trailing 's') as tracking key for backward compatibility. $trackingKey = if ($connectionMode -eq 'Credentials') { 'Credential' } else { $connectionMode } $workloadTrackingKey = "$Workload-$trackingKey" if (-not ($Script:M365ConnectedToWorkloads -contains $workloadTrackingKey)) { $data.Add('ConnectionMode', $connectionMode) if (-not $data.ContainsKey('Tenant')) { if (-not [System.String]::IsNullOrEmpty($InboundParameters.TenantId)) { $data.Add('Tenant', $InboundParameters.TenantId) } elseif ($null -ne $InboundParameters.Credential) { try { $tenantId = $InboundParameters.Credential.Username.Split('@')[1] $data.Add('Tenant', $tenantId) if (-not $Script:M365DSCTelemetryConnectionToGraphParams.ContainsKey('TenantId')) { $Script:M365DSCTelemetryConnectionToGraphParams.Add('TenantId', $tenantId) } } catch { Write-Verbose -Message $_ } } } Add-M365DSCTelemetryEvent -Data $data -Type 'Connection' $Script:M365ConnectedToWorkloads += $workloadTrackingKey } #endregion return $connectionMode } <# .SYNOPSIS Gets the authentication mode based on the specified parameters. .DESCRIPTION This function gets the used authentication mode based on the specified parameters .PARAMETER Parameters Specifies a hashtable of parameters to use for determining the authentication mode. The keys and values in the hashtable should match the parameters of the Connect-M365Tenant function. .FUNCTIONALITY Internal #> function Get-M365DSCAuthenticationMode { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Parameters ) # Cache frequently accessed values to reduce hashtable lookups $applicationId = $Parameters.ApplicationId $tenantId = $Parameters.TenantId $credential = $Parameters.Credential # Check service principal authentication modes first (most common in automation) if ($applicationId -and $tenantId) { if ($Parameters.CertificateThumbprint) { return 'ServicePrincipalWithThumbprint' } if ($Parameters.ApplicationSecret) { return 'ServicePrincipalWithSecret' } if ($Parameters.CertificatePath -and $Parameters.CertificatePassword) { return 'ServicePrincipalWithPath' } } # Check credential-based authentication if ($credential) { if ($applicationId) { return 'CredentialsWithApplicationId' } if ($tenantId) { return 'CredentialsWithTenantId' } return 'Credentials' } # Check other authentication modes if ($Parameters.ManagedIdentity) { return 'ManagedIdentity' } if ($Parameters.AccessTokens) { return 'AccessTokens' } # Default to interactive return 'Interactive' } <# .SYNOPSIS Retrieves the telemetry connection parameters for the current session. .DESCRIPTION This function retrieves the telemetry connection parameters for the current session. .FUNCTIONALITY Internal. #> function Get-M365DSCTelemetryConnectionParameter { [CmdletBinding()] param () $Script:M365DSCTelemetryConnectionToGraphParams.Clone() } <# .SYNOPSIS Sets the telemetry connection parameters for the current session. .DESCRIPTION This function sets the telemetry connection parameters for the current session. .PARAMETER Parameters Specifies a hashtable of parameters to set for the telemetry connection. The keys and values in the hashtable should match the parameters of the Connect-M365Tenant function. .FUNCTIONALITY Internal. #> function Set-M365DSCTelemetryConnectionParameter { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [hashtable]$Parameters ) $Script:M365DSCTelemetryConnectionToGraphParams = $Parameters.Clone() } <# .SYNOPSIS Records a failed connection attempt for the running export. .DESCRIPTION While an export is running, stores the failure message under the workload and connection mode key so later attempts fail immediately. .PARAMETER FailureKey Specifies the cache key in the form 'Workload-ConnectionMode'. .PARAMETER Message Specifies the failure message. #> function Register-M365DSCConnectionFailure { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $FailureKey, [Parameter(Mandatory = $true)] [System.String] $Message ) if ($Global:M365DSCExportInProgress) { $Script:M365DSCConnectionFailures[$FailureKey] = $Message } } <# .SYNOPSIS Clears the connection failure cache. .DESCRIPTION Removes every memoized connection failure so that the next export attempts each workload again. #> function Reset-M365DSCConnectionFailureCache { [CmdletBinding()] param () $Script:M365DSCConnectionFailures = @{} } Export-ModuleMember -Function @( 'Get-M365DSCAuthenticationMode', 'Get-M365DSCComponentsWithMostSecureAuthenticationType', 'Get-M365DSCResourcePropertyNameMap', 'Get-M365DSCTelemetryConnectionParameter', 'New-M365DSCConnection', 'Reset-M365DSCConnectionFailureCache', 'Set-M365DSCTelemetryConnectionParameter' ) |