diff --git a/DscResources.psd1 b/DscResources.psd1 index 00869be..a938f55 100644 --- a/DscResources.psd1 +++ b/DscResources.psd1 @@ -1,7 +1,7 @@ @{ 'M365DSC.CompositeResources' = 'latestMatchingMicrosoft365DSC' 'M365DSCTools' = 'latest' - 'Microsoft365DSC' = '1.24.904.1' + 'Microsoft365DSC' = '1.24.1002.1' # Generic modules 'Pester' = 'latest' diff --git a/Pipelines/build-template.yaml b/Pipelines/build-template.yaml index 77f9b9e..9af2dc5 100644 --- a/Pipelines/build-template.yaml +++ b/Pipelines/build-template.yaml @@ -63,6 +63,7 @@ stages: filePath: './CICD/Scripts/PreBuild.ps1' errorActionPreference: 'Stop' failOnStderr: true + pwsh: true # Run the Build task and compile the MOF files - task: PowerShell@2 diff --git a/Scripts/CacheModules.ps1 b/Scripts/CacheModules.ps1 index d83378b..8c51b98 100644 --- a/Scripts/CacheModules.ps1 +++ b/Scripts/CacheModules.ps1 @@ -125,11 +125,11 @@ if ($packageExists -eq $false) if ($result -eq $true) { - Write-Log -Object 'Successfully retrieved all required modules from Azure Blob Storage' + Write-Log -Object 'Successfully uploaded all required modules to Azure Blob Storage' } else { - Write-Log -Object '[ERROR] Unable to retrieve all required modules from Azure Blob Storage' -Failure + Write-Log -Object '[ERROR] Unable to upload all required modules to Azure Blob Storage' -Failure Write-Host '##vso[task.complete result=Failed;]Failed' } } diff --git a/Scripts/M365Configuration.ps1 b/Scripts/M365Configuration.ps1 index fefeb88..292d2d1 100644 --- a/Scripts/M365Configuration.ps1 +++ b/Scripts/M365Configuration.ps1 @@ -4,17 +4,34 @@ node localhost { + $azureAppCreds = $ConfigurationData.NonNodeData.AppCredentials | Where-Object -FilterScript { $_.Workload -eq 'Azure' } $azureadAppCreds = $ConfigurationData.NonNodeData.AppCredentials | Where-Object -FilterScript { $_.Workload -eq 'AzureAD' } + $azuredoAppCreds = $ConfigurationData.NonNodeData.AppCredentials | Where-Object -FilterScript { $_.Workload -eq 'AzureDevOps' } + $defenderAppCreds = $ConfigurationData.NonNodeData.AppCredentials | Where-Object -FilterScript { $_.Workload -eq 'Defender' } $exchangeAppCreds = $ConfigurationData.NonNodeData.AppCredentials | Where-Object -FilterScript { $_.Workload -eq 'Exchange' } + $fabricAppCreds = $ConfigurationData.NonNodeData.AppCredentials | Where-Object -FilterScript { $_.Workload -eq 'Fabric' } $intuneAppCreds = $ConfigurationData.NonNodeData.AppCredentials | Where-Object -FilterScript { $_.Workload -eq 'Intune' } $officeAppCreds = $ConfigurationData.NonNodeData.AppCredentials | Where-Object -FilterScript { $_.Workload -eq 'Office365' } $onedriveAppCreds = $ConfigurationData.NonNodeData.AppCredentials | Where-Object -FilterScript { $_.Workload -eq 'OneDrive' } $plannerAppCreds = $ConfigurationData.NonNodeData.AppCredentials | Where-Object -FilterScript { $_.Workload -eq 'Planner' } $powerplatformAppCreds = $ConfigurationData.NonNodeData.AppCredentials | Where-Object -FilterScript { $_.Workload -eq 'PowerPlatform' } $securitycomplianceAppCreds = $ConfigurationData.NonNodeData.AppCredentials | Where-Object -FilterScript { $_.Workload -eq 'SecurityCompliance' } + $sentinelAppCreds = $ConfigurationData.NonNodeData.AppCredentials | Where-Object -FilterScript { $_.Workload -eq 'Sentinel' } $sharepointAppCreds = $ConfigurationData.NonNodeData.AppCredentials | Where-Object -FilterScript { $_.Workload -eq 'SharePoint' } $teamsAppCreds = $ConfigurationData.NonNodeData.AppCredentials | Where-Object -FilterScript { $_.Workload -eq 'Teams' } + # Azure Composite Resource + if ($null -ne $azureAppCreds -and $ConfigurationData.NonNodeData.Environment.UsedWorkloads.Azure -eq $true) + { + Azure 'Azure_Configuration' + { + ApplicationId = $azureAppCreds.ApplicationId + TenantId = $ConfigurationData.NonNodeData.Environment.TenantId + CertificateThumbprint = $azureAppCreds.CertThumbprint + } + } + + # Azure AD / Entra ID Composite Resource if ($null -ne $azureadAppCreds -and $ConfigurationData.NonNodeData.Environment.UsedWorkloads.AzureAD -eq $true) { AzureAD 'AzureAD_Configuration' @@ -25,6 +42,29 @@ } } + # Azure DevOps Composite Resource + if ($null -ne $azuredoAppCreds -and $ConfigurationData.NonNodeData.Environment.UsedWorkloads.AzureDevOps -eq $true) + { + AzureDevOps 'AzureDevOps_Configuration' + { + ApplicationId = $azuredoAppCreds.ApplicationId + TenantId = $ConfigurationData.NonNodeData.Environment.TenantId + CertificateThumbprint = $azuredoAppCreds.CertThumbprint + } + } + + # Defender Composite Resource + if ($null -ne $defenderAppCreds -and $ConfigurationData.NonNodeData.Environment.UsedWorkloads.Defender -eq $true) + { + Defender 'Defender_Configuration' + { + ApplicationId = $defenderAppCreds.ApplicationId + TenantId = $ConfigurationData.NonNodeData.Environment.TenantId + CertificateThumbprint = $defenderAppCreds.CertThumbprint + } + } + + # Exchange Composite Resource if ($null -ne $exchangeAppCreds -and $ConfigurationData.NonNodeData.Environment.UsedWorkloads.Exchange -eq $true) { Exchange 'Exchange_Configuration' @@ -35,6 +75,18 @@ } } + # Fabric Composite Resource + if ($null -ne $fabricAppCreds -and $ConfigurationData.NonNodeData.Environment.UsedWorkloads.Fabric -eq $true) + { + Fabric 'Fabric_Configuration' + { + ApplicationId = $fabricAppCreds.ApplicationId + TenantId = $ConfigurationData.NonNodeData.Environment.TenantId + CertificateThumbprint = $fabricAppCreds.CertThumbprint + } + } + + # Intune Composite Resource if ($null -ne $intuneAppCreds -and $ConfigurationData.NonNodeData.Environment.UsedWorkloads.Intune -eq $true) { Intune 'Intune_Configuration' @@ -45,6 +97,7 @@ } } + # Office 365 Composite Resource if ($null -ne $officeAppCreds -and $ConfigurationData.NonNodeData.Environment.UsedWorkloads.Office365 -eq $true) { Office365 'Office365_Configuration' @@ -55,6 +108,7 @@ } } + # OneDrive Composite Resource if ($null -ne $onedriveAppCreds -and $ConfigurationData.NonNodeData.Environment.UsedWorkloads.OneDrive -eq $true) { OneDrive 'OneDrive_Configuration' @@ -65,6 +119,7 @@ } } + # Planner Composite Resource if ($null -ne $plannerAppCreds -and $ConfigurationData.NonNodeData.Environment.UsedWorkloads.Planner -eq $true) { Planner 'Planner_Configuration' @@ -75,6 +130,7 @@ } } + # PowerPlatform Composite Resource if ($null -ne $powerplatformAppCreds -and $ConfigurationData.NonNodeData.Environment.UsedWorkloads.PowerPlatform -eq $true) { PowerPlatform 'PowerPlatform_Configuration' @@ -85,6 +141,7 @@ } } + # Security Compliance Composite Resource if ($null -ne $securitycomplianceAppCreds -and $ConfigurationData.NonNodeData.Environment.UsedWorkloads.SecurityCompliance -eq $true) { SecurityCompliance 'SecurityCompliance_Configuration' @@ -95,6 +152,18 @@ } } + # Sentinel Composite Resource + if ($null -ne $sentinelAppCreds -and $ConfigurationData.NonNodeData.Environment.UsedWorkloads.Sentinel -eq $true) + { + Sentinel 'Sentinel_Configuration' + { + ApplicationId = $sentinelAppCreds.ApplicationId + TenantId = $ConfigurationData.NonNodeData.Environment.TenantId + CertificateThumbprint = $sentinelAppCreds.CertThumbprint + } + } + + # SharePoint Composite Resource if ($null -ne $sharepointAppCreds -and $ConfigurationData.NonNodeData.Environment.UsedWorkloads.SharePoint -eq $true) { SharePoint 'SharePoint_Configuration' @@ -105,6 +174,7 @@ } } + # Teams Composite Resource if ($null -ne $teamsAppCreds -and $ConfigurationData.NonNodeData.Environment.UsedWorkloads.Teams -eq $true) { Teams 'Teams_Configuration' diff --git a/Scripts/PreBuild.ps1 b/Scripts/PreBuild.ps1 index 3416328..7dcd7a4 100644 --- a/Scripts/PreBuild.ps1 +++ b/Scripts/PreBuild.ps1 @@ -12,10 +12,6 @@ data files as input data. The script also performs quality checks on the data files, to make sure the data is correct and valid. -.PARAMETER SortObject - When specified, the merged output is sorted alphabetically. This is a time consuming - task, so should only be used when the solution only contains a few environments. - .EXAMPLE .\build.ps1 #> @@ -23,11 +19,7 @@ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Write-Host needed for Azure DevOps logging')] [CmdletBinding()] param -( - [Parameter()] - [Switch] - $SortOutput -) +() ######## SCRIPT VARIABLES ######## $configFileSeparator = '#' @@ -41,6 +33,8 @@ $qaTestPath = Join-Path -Path $testsFolder -ChildPath 'Run-QATests.ps1' -Resolve $qaCheckErrors = $false $excludeAvailableAsResource = @('*UniqueId','*IsSingleInstance','NonNodeData.Environment.Tokens*') +$mergeKeys = @('NodeName', 'Identity', 'Id', 'UniqueId', 'SettingDefinitionId') +$sortKeys = @('Priority') ######## START SCRIPT ######## @@ -119,7 +113,7 @@ foreach ($mandatoryConfigFile in $mandatoryConfigFiles) } else { - Write-Log -Object ' Tests passed!' + Write-Log -Object ' All tests have passed!' } $mandatoryConfigNode = Get-Node -InputObject $mandatoryConfig @@ -130,7 +124,7 @@ foreach ($mandatoryConfigFile in $mandatoryConfigFiles) Write-Log -Object "Merging file: $($mandatoryConfigFile.Name)" Write-Log -Object '----------------------------------------------------' $mandatoryConfigNextFragment = Import-PSDataFile $mandatoryConfigFile.Fullname - + Write-Log -Object ' Testing if data adheres to the data schema' $mandatoryTestResults = $null $mandatoryTestResults = Test-M365DSCPowershellDataFile -Test 'TypeValue' -InputObject $mandatoryConfigNextFragment -ExcludeAvailableAsResource $excludeAvailableAsResource -PesterOutputObject @@ -141,13 +135,13 @@ foreach ($mandatoryConfigFile in $mandatoryConfigFiles) } else { - Write-Log -Object ' Tests passed!' + Write-Log -Object ' All tests have passed!' } $mandatoryConfigNextFragmentNode = Get-Node -InputObject $mandatoryConfigNextFragment Write-Log -Object ' Merging files' - $mandatoryConfigNode = Merge-ObjectGraph -InputObject $mandatoryConfigNextFragmentNode -Template $mandatoryConfigNode -PrimaryKey 'NodeName', 'Id', 'Identity', 'UniqueId' + $mandatoryConfigNode = Merge-ObjectGraph -InputObject $mandatoryConfigNextFragmentNode -Template $mandatoryConfigNode -PrimaryKey $mergeKeys } $c++ } @@ -178,7 +172,7 @@ foreach ($basicConfigFile in $basicConfigFiles) } else { - Write-Log -Object ' Tests passed!' + Write-Log -Object ' All tests have passed!' } $basicConfigNode = Get-Node -InputObject $basicConfig @@ -200,24 +194,17 @@ foreach ($basicConfigFile in $basicConfigFiles) } else { - Write-Log -Object ' Tests passed!' + Write-Log -Object ' All tests have passed!' } $basicConfigNextFragmentNode = Get-Node -InputObject $basicConfigNextFragment Write-Log -Object 'Merging files' - $basicConfigNode = Merge-ObjectGraph -InputObject $basicConfigNextFragmentNode -Template $basicConfigNode -PrimaryKey 'NodeName', 'Id', 'Identity', 'UniqueId' + $basicConfigNode = Merge-ObjectGraph -InputObject $basicConfigNextFragmentNode -Template $basicConfigNode -PrimaryKey $mergeKeys } $c++ } -if ($SortOutput -eq $false) -{ - Write-Log -Object 'Sorting Basic configuration data on Priority' - $basicConfigNode = $basicConfigNode | Sort-ObjectGraph -PrimaryKey 'NodeName', 'Identity', 'Id', 'UniqueId', 'Priority' -MaxDepth 20 - Write-Log -Object ' ' -} - Write-Log -Object 'Testing if Mandatory data is present in Basic data' $mandatoryTestResults = Test-M365DSCPowershellDataFile -InputObject $basicConfigNode -MandatoryObject $mandatoryConfigNode -Test 'Mandatory' -MandatoryAction 'Present' -PesterOutputObject @@ -228,7 +215,7 @@ if ($mandatoryTestResults.Result -ne 'Passed') } else { - Write-Log -Object ' Tests passed!' + Write-Log -Object ' All tests have passed!' } Write-Log -Object ' ' @@ -255,7 +242,7 @@ foreach ($environment in $environments.Environment) Write-Log -Object "Importing file: $($envDataFile.Name)" Write-Log -Object '-------------------------------------------' $envConfig = Import-PSDataFile $envDataFile.FullName - + Write-Log -Object ' Testing if data adheres to the data schema' $envTestResults = Test-M365DSCPowershellDataFile -Test 'TypeValue' -InputObject $envConfig -ExcludeAvailableAsResource $excludeAvailableAsResource -PesterOutputObject if ($envTestResults.Result -ne 'Passed') @@ -265,7 +252,7 @@ foreach ($environment in $environments.Environment) } else { - Write-Log -Object ' Tests passed!' + Write-Log -Object ' All tests have passed!' } $envConfigNode = Get-Node -InputObject $envConfig @@ -276,7 +263,7 @@ foreach ($environment in $environments.Environment) Write-Log -Object "Merging file: $($envDataFile.Name)" Write-Log -Object '-------------------------------------------' $envConfigNextFragment = Import-PSDataFile $envDataFile.FullName - + Write-Log -Object ' Testing if data adheres to the data schema' $envTestResults = $null $envTestResults = Test-M365DSCPowershellDataFile -Test 'TypeValue' -InputObject $envConfigNextFragment -ExcludeAvailableAsResource $excludeAvailableAsResource -PesterOutputObject @@ -287,13 +274,13 @@ foreach ($environment in $environments.Environment) } else { - Write-Log -Object ' Tests passed!' + Write-Log -Object ' All tests have passed!' } $envConfigNextFragmentNode = Get-Node -InputObject $envConfigNextFragment Write-Log -Object ' Merging files' - $envConfigNode = Merge-ObjectGraph -InputObject $envConfigNextFragmentNode -Template $envConfigNode -PrimaryKey 'NodeName', 'Id', 'Identity', 'UniqueId' + $envConfigNode = Merge-ObjectGraph -InputObject $envConfigNextFragmentNode -Template $envConfigNode -PrimaryKey $mergeKeys } $c++ } @@ -310,7 +297,7 @@ foreach ($environment in $environments.Environment) } else { - Write-Log -Object ' Tests passed!' + Write-Log -Object ' All tests have passed!' } Write-Log -Object ' ' @@ -341,17 +328,11 @@ foreach ($environment in $envsConfig) Write-Log -Object ' ' Write-Log -Object 'Merging basic config with environment-specific config' - $mergedConfigDataNode = Merge-ObjectGraph -InputObject $environment.Config -Template $basicConfigNode -PrimaryKey 'NodeName', 'Identity', 'Id', 'UniqueId' + $mergedConfigDataNode = Merge-ObjectGraph -InputObject $environment.Config -Template $basicConfigNode -PrimaryKey $mergeKeys Write-Log -Object 'Exporting Original ConfigData to file' - if ($SortOutput) - { - $psdStringData = $mergedConfigDataNode | Sort-ObjectGraph -PrimaryKey 'NodeName', 'Identity', 'Id', 'UniqueId', 'Priority' -MaxDepth 20 | ConvertTo-Expression - } - else - { - $psdStringData = $mergedConfigDataNode | ConvertTo-Expression - } + Write-Log -Object " Sorting and exporting data on keys: $($sortKeys -join ", ")" + $psdStringData = $mergedConfigDataNode | Sort-ObjectGraph -PrimaryKey $sortKeys -MaxDepth 20 | ConvertTo-Expression $originalPsdPath = Join-Path -Path $outputPathDataFile -ChildPath "$($environment.Name)_Original.psd1" Set-Content -Path $originalPsdPath -Value $psdStringData @@ -368,14 +349,7 @@ foreach ($environment in $envsConfig) $mergedConfigDataNode = [System.Management.Automation.PSSerializer]::Deserialize($Obj_Result_Serialized) Write-Log -Object 'Exporting Tokenized ConfigData to file' - if ($SortOutput) - { - $psdStringData = $mergedConfigDataNode | Sort-ObjectGraph -PrimaryKey 'NodeName', 'Identity', 'Id', 'UniqueId', 'Priority' -MaxDepth 20 | ConvertTo-Expression - } - else - { - $psdStringData = $mergedConfigDataNode | ConvertTo-Expression - } + $psdStringData = $mergedConfigDataNode | ConvertTo-Expression $finalPsdPath = Join-Path -Path $outputPathDataFile -ChildPath "$($environment.Name).psd1" Set-Content -Path $finalPsdPath -Value $psdStringData Write-Log -Object ' ' @@ -393,7 +367,7 @@ foreach ($environment in $envsConfig) } else { - Write-Log -Object ' Tests passed!' + Write-Log -Object ' All tests have passed!' } $current++ }