diff --git a/CHANGELOG.md b/CHANGELOG.md index 404c9b1ccd..53d0a49fd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,9 @@ * Fix `Compare-PSCustomObjectArrays` by allowing empty arrays as input FIXES [#4952](https://github.com/microsoft/Microsoft365DSC/issues/4952) * MISC + * Improve module updates and PowerShell Core support across the DSC + resources. + FIXES [#4941](https://github.com/microsoft/Microsoft365DSC/issues/4941) * Replace some `Write-Host` occurrences in core engine with appropriate alternatives. FIXES [#4943](https://github.com/microsoft/Microsoft365DSC/issues/4943) diff --git a/Modules/Microsoft365DSC/Dependencies/Manifest.psd1 b/Modules/Microsoft365DSC/Dependencies/Manifest.psd1 index ffb61d0613..651a963737 100644 --- a/Modules/Microsoft365DSC/Dependencies/Manifest.psd1 +++ b/Modules/Microsoft365DSC/Dependencies/Manifest.psd1 @@ -96,6 +96,18 @@ ModuleName = 'PnP.PowerShell' RequiredVersion = '1.12.0' }, + @{ + ModuleName = 'PSDesiredStateConfiguration' + RequiredVersion = '1.1' + PowerShellCore = $false + }, + @{ + ModuleName = 'PSDesiredStateConfiguration' + RequiredVersion = '2.0.7' + PowerShellCore = $true + ExplicitLoading = $true + Prefix = 'Pwsh' + }, @{ ModuleName = 'ReverseDSC' RequiredVersion = '2.0.0.20' diff --git a/Modules/Microsoft365DSC/Modules/M365DSCReport.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCReport.psm1 index d46e9ccfb6..823e23ba30 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCReport.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCReport.psm1 @@ -419,12 +419,21 @@ function Get-Base64EncodedImage { $mimeType = "image/jpeg" } + if($icon.Extension.endsWith("png")) { $mimeType = "image/png" } - $base64EncodedImage = [System.Convert]::ToBase64String((Get-Content -Path $iconPath -Encoding Byte -ReadCount 0)) + if ($PSVersionTable.PSEdition -eq 'Core') + { + $base64EncodedImage = [System.Convert]::ToBase64String((Get-Content -Path $IconPath -AsByteStream -ReadCount 0)) + } + else + { + $base64EncodedImage = [System.Convert]::ToBase64String((Get-Content -Path $iconPath -Encoding Byte -ReadCount 0)) + } + return $("data:$($mimeType);base64,$($base64EncodedImage)") } else @@ -736,7 +745,15 @@ function Compare-M365DSCConfigurations [Array]$DestinationObject = $DestinationObject | Where-Object -FilterScript { $_.ResourceName -notin $ExcludedResources } } - $dscResourceInfo = Get-DSCResource -Module 'Microsoft365DSC' + $isPowerShellCore = $PSVersionTable.PSEdition -eq 'Core' + if ($isPowerShellCore) + { + $dscResourceInfo = Get-PwshDSCResource -Module 'Microsoft365DSC' + } + else + { + $dscResourceInfo = Get-DSCResource -Module 'Microsoft365DSC' + } # Loop through all items in the source array $i = 1 foreach ($sourceResource in $SourceObject) diff --git a/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 index b9fececa3e..7fad40386a 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 @@ -1448,6 +1448,7 @@ function Export-M365DSCConfiguration } $Script:M365DSCDependenciesValidated = $false +$Script:IsPowerShellCore = $PSVersionTable.PSEdition -eq 'Core' <# .Description @@ -1474,7 +1475,7 @@ function Confirm-M365DSCDependencies { $ErrorMessage += ' * ' + $invalidDependency.ModuleName + "`r`n" } - $ErrorMessage += 'Please run Update-M365DSCDependencies with scope "currentUser" or as Administrator.' + $ErrorMessage += 'Please run Update-M365DSCDependencies as Administrator.' $ErrorMessage += 'Please run Uninstall-M365DSCOutdatedDependencies.' $Script:M365DSCDependenciesValidated = $false Add-M365DSCEvent -Message $ErrorMessage -EntryType 'Error' ` @@ -1519,6 +1520,17 @@ function Import-M365DSCDependencies foreach ($dependency in $dependencies) { + if ($dependency.PowerShellCore -and -not $Script:IsPowerShellCore) + { + Write-Verbose -Message "Skipping module {$($dependency.ModuleName)} as it is not compatible with Windows PowerShell." + continue + } + elseif ($dependency.PowerShellCore -eq $false -and $Script:IsPowerShellCore) + { + Write-Verbose -Message "Skipping module {$($dependency.ModuleName)} as it is not compatible with PowerShell Core." + continue + } + Import-Module $dependency.ModuleName -RequiredVersion $dependency.RequiredVersion -Force -Global:$Global } } @@ -3093,6 +3105,16 @@ function Update-M365DSCDependencies { if (-not $Force) { + if ($dependency.PowerShellCore -and -not $Script:IsPowerShellCore) + { + Write-Verbose -Message "The dependency {$($dependency.ModuleName)} requires PowerShell Core. Skipping." + continue + } + elseif ($dependency.PowerShellCore -eq $false -and $Script:IsPowerShellCore) + { + Write-Verbose -Message "The dependency {$($dependency.ModuleName)} requires Windows PowerShell. Skipping." + continue + } $found = Get-Module $dependency.ModuleName -ListAvailable | Where-Object -FilterScript { $_.Version -eq $dependency.RequiredVersion } } @@ -3109,10 +3131,21 @@ function Update-M365DSCDependencies } catch { - Write-Verbose -Message "Couldn't retrieve Windows Principal. One possible cause is that the current environment is not Windows OS." + Write-Verbose -Message "Couldn't retrieve Windows Principal. One possible cause is that the current environment is not a Windows OS." } if (-not $errorFound) { + if (-not $dependency.PowerShellCore -and $Script:IsPowerShellCore) + { + Write-Warning "The dependency {$($dependency.ModuleName)} does not support PowerShell Core. Please run Update-M365DSCDependencies in Windows PowerShell." + continue + } + elseif ($dependency.PowerShellCore -and -not $Script:IsPowerShellCore) + { + Write-Warning "The dependency {$($dependency.ModuleName)} requires PowerShell Core. Please run Update-M365DSCDependencies in PowerShell Core." + continue + } + Write-Information -MessageData "Installing $($dependency.ModuleName) version {$($dependency.RequiredVersion)}" Remove-Module $dependency.ModuleName -Force -ErrorAction SilentlyContinue if ($dependency.ModuleName -like 'Microsoft.Graph*') @@ -3124,6 +3157,19 @@ function Update-M365DSCDependencies } } + if ($dependency.ExplicitLoading) + { + Remove-Module $dependency.ModuleName -Force -ErrorAction SilentlyContinue + if ($dependency.Prefix) + { + Import-Module $dependency.ModuleName -Global -Prefix $dependency.Prefix -Force + } + else + { + Import-Module $dependency.ModuleName -Global -Force + } + } + if (-not $found -and $validateOnly) { $returnValue += $dependency @@ -3205,6 +3251,16 @@ function Uninstall-M365DSCOutdatedDependencies Write-Progress -Activity 'Scanning Dependencies' -PercentComplete ($i / $allDependenciesExceptAuth.Count * 100) try { + if ($dependency.PowerShellCore -and -not $Script:IsPowerShellCore) + { + Write-Verbose -Message "Skipping module {$($dependency.ModuleName)} as it is managed by PowerShell Core." + continue + } + elseif ($dependency.PowerShellCore -eq $false -and $Script:IsPowerShellCore) + { + Write-Verbose -Message "Skipping module {$($dependency.ModuleName)} as it is managed by Windows PowerShell." + continue + } $found = Get-Module $dependency.ModuleName -ListAvailable | Where-Object -FilterScript { $_.Version -ne $dependency.RequiredVersion } foreach ($foundModule in $found) { @@ -3579,7 +3635,14 @@ function Get-M365DSCExportContentForResource { if ($Script:AllM365DscResources.Count -eq 0) { - $Script:AllM365DscResources = Get-DscResource -Module 'Microsoft365Dsc' + if ($Script:IsPowerShellCore) + { + $Script:AllM365DscResources = Get-PwshDscResource -Module 'Microsoft365Dsc' + } + else + { + $Script:AllM365DscResources = Get-DscResource -Module 'Microsoft365Dsc' + } } $Resource = $Script:AllM365DscResources.Where({ $_.Name -eq $ResourceName }) @@ -4324,7 +4387,14 @@ function Create-M365DSCResourceExample $ResourceName ) - $resource = Get-DscResource -Name $ResourceName + if ($Script:IsPowerShellCore) + { + $resource = Get-PwshDscResource -Name $ResourceName + } + else + { + $resource = Get-DscResource -Name $ResourceName + } $params = Get-DSCFakeParameters -ModulePath $resource.Path @@ -4409,7 +4479,14 @@ function New-M365DSCMissingResourcesExample { $location = $PSScriptRoot - $m365Resources = Get-DscResource -Module Microsoft365DSC | Select-Object -ExpandProperty Name + if ($Script:IsPowerShellCore) + { + $m365Resources = Get-PwshDscResource -Module Microsoft365DSC | Select-Object -ExpandProperty Name + } + else + { + $m365Resources = Get-DscResource -Module Microsoft365DSC | Select-Object -ExpandProperty Name + } $examplesPath = Join-Path $location -ChildPath '..\Examples\Resources' $examples = Get-ChildItem -Path $examplesPath | Where-Object { $_.PsIsContainer } | Select-Object -ExpandProperty Name @@ -4511,7 +4588,7 @@ function Update-M365DSCModule ) try { - Update-Module -Name 'Microsoft365DSC' -ErrorAction Stop -Scope $Scope + Update-Module -Name 'Microsoft365DSC' -ErrorAction Stop } catch { @@ -4765,7 +4842,14 @@ function Get-M365DSCConfigurationConflict $parsedContent = ConvertTo-DSCObject -Content $ConfigurationContent $resourcesPrimaryIdentities = @() - $resourcesInModule = Get-DSCResource -Module 'Microsoft365DSC' + if ($Script:IsPowerShellCore) + { + $resourcesInModule = Get-PwshDSCResource -Module 'Microsoft365DSC' + } + else + { + $resourcesInModule = Get-DSCResource -Module 'Microsoft365DSC' + } foreach ($component in $parsedContent) { $resourceDefinition = $resourcesInModule | Where-Object -FilterScript {$_.Name -eq $component.ResourceName} diff --git a/Tests/TestHarness.psm1 b/Tests/TestHarness.psm1 index ecc798f884..a1ccd89269 100644 --- a/Tests/TestHarness.psm1 +++ b/Tests/TestHarness.psm1 @@ -38,6 +38,7 @@ function Invoke-TestHarness } Import-Module -Name "$repoDir/Modules/Microsoft365DSC/Microsoft365DSC.psd1" + Import-Module -Name PSDesiredStateConfiguration -Global -Prefix 'Pwsh' -Force $testsToRun = @() # Run Unit Tests diff --git a/docs/docs/user-guide/get-started/powershell7-support.md b/docs/docs/user-guide/get-started/powershell7-support.md index cb869406b4..b12e6f1041 100644 --- a/docs/docs/user-guide/get-started/powershell7-support.md +++ b/docs/docs/user-guide/get-started/powershell7-support.md @@ -41,10 +41,10 @@ that location or use PowerShell 5.1 to install the modules using 'Install-Module Connect-PnPOnline: Could not load file or assembly 'System.IdentityModel.Tokens.Jwt, Version=6.12.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. Could not find or load a specific file. (0x80131621) ``` -# PSDesiredStateConfiguration Needs to be Installed Separately +# PSDesiredStateConfiguration needs to be installed separately -Starting with PowerShell 7.2, the core Desired State Configuration module (PSdesiredStateConfiguration) has been decoupled from the core PowerShell build and now need to be installed separately. In a PowerShell 7+ console, you can install the module by running the command: +Starting with PowerShell 7.2, the core Desired State Configuration module (PSdesiredStateConfiguration) has been decoupled from the core PowerShell build and now needs to be installed separately. In an administrative PowerShell 7+ console, you can install the module by running the command: ```powershell -Install-Module PSDesiredStateConfiguration -Force +Update-M365DSCDependencies -Scope AllUsers ```