Microsoft365DSC icon indicating copy to clipboard operation
Microsoft365DSC copied to clipboard

Add module pinning

Open FabienTschanz opened this issue 6 months ago • 2 comments

Pull Request (PR) description

This PR implements module pinning during module load in Microsoft365DSC. Now it is possible to have n versions of a module installed. As long as the required version from the manifest is installed, it will always load the one specified there. If a module is loaded with the wrong version, it will be unloaded and reimported in the session.

For this to work, two new cmdlets were introduced:

  • Confirm-M365DSCModuleDependency - A public cmdlet which fetches the required dependencies for a module from the settings.json and passes them to Confirm-M365DSCLoadedModule.
  • Confirm-M365DSCLoadedModule - An internal cmdlet which checks the passed in modules if they are loaded with the correct version and if not, it reloads them with the correct version.

In all settings.json files, an entry requiredModules was added which contains the modules (e.g. Microsoft.Graph.Authentication, MicrosoftTeams etc.) that have to be loaded before running the DSC resource. The import of the modules is done by running Confirm-M365DSCModuleDependency -ModuleName <DSC_Resource> when the DSC resource is loaded. This ensures that the required modules are loaded on startup and before a *-TargetResource function is executed.

To improve performance, already validated modules are excluded from a re-check. If this proves to be problematic, it can be disabled in a future release. Also, it is possible to disable the entire check using a global variable: SkipModuleValidation. If this is set to $True, it will skip the entire module import process. This is especially useful if we're running tests in an isolated matter.

The following code was used to generate the settings.json files. It leverages the PowerShell Abstract Syntax Tree to find all of the cmdlets, process their corresponding modules and adds them to the settings.json files.

$MaximumFunctionCount = 32767
$m365dscModules = (Import-PowerShellDataFile -Path D:\M365DSC\Microsoft365DSC\Modules\Microsoft365DSC\Dependencies\Manifest.psd1).Dependencies.ModuleName
$dscResourcesWithModules = Get-ChildItem -Path D:\M365DSC\Microsoft365DSC\Modules\Microsoft365DSC\DSCResources\ -Recurse -Filter *.psm1 | Foreach-Object -Process {
    Write-Host "Processing $($_.FullName)" -ForegroundColor Cyan
    $ast = [System.Management.Automation.Language.Parser]::ParseFile($_.FullName, [ref]$null, [ref]$null)
    $targetResourceFunctions = $ast.FindAll(
        {
            param($Item)
            return (
                ($Item -is [System.Management.Automation.Language.FunctionDefinitionAst]) -and
                ($Item.Name -like '*-TargetResource')
            )
        }, $true
    )
    $commands = $targetResourceFunctions | ForEach-Object -Process {
        $_.FindAll(
            {
                 param($Item)
                 return (
                     ($Item -is [System.Management.Automation.Language.CommandAst])
                 )
            }, $true
        )
    } | Foreach-Object -Process {
        $_.CommandElements[0]
    } | Select-Object -ExpandProperty Value -Unique
    $modules = @()
    foreach ($command in $commands) {
        $module = Get-Command -Name $command -ErrorAction SilentlyContinue | Select-Object -ExpandProperty ModuleName
        if ($module -in $m365dscModules) {
            if ($module -like "Microsoft.Graph.*" -and -not $modules -contains "Microsoft.Graph.Authentication") {
                $modules += "Microsoft.Graph.Authentication"
            }
            $modules += $module
        }
    }
    $modules = $modules | Sort-Object -Unique
    @{
        ResourceName = $_.BaseName
        Modules      = [array]$modules
    }
}

$dscResourcesWithModules

foreach ($dscResource in $dscResourcesWithModules) {
    Write-Host "Resource: $($dscResource.ResourceName)" -ForegroundColor Yellow
    $settingsJson = Get-Content -Path "D:\M365DSC\Microsoft365DSC\Modules\Microsoft365DSC\DSCResources\$($dscResource.ResourceName)\settings.json" -Raw | ConvertFrom-Json -AsHashtable
    $settingsJson.Add('requiredModules', [array]$dscResource.Modules)
    $settingsJson | ConvertTo-Json -Depth 10 | Set-Content -Path "D:\M365DSC\Microsoft365DSC\Modules\Microsoft365DSC\DSCResources\$($dscResource.ResourceName)\settings.json"
}

This Pull Request (PR) fixes the following issues

  • Fixes #6168

Task list

  • [x] Added an entry to the change log under the Unreleased section of the file CHANGELOG.md. Entry should say what was changed and how that affects users (if applicable), and reference the issue being resolved (if applicable).
  • [ ] Resource parameter descriptions added/updated in the schema.mof.
  • [ ] Resource documentation added/updated in README.md.
  • [x] Resource settings.json file contains all required permissions.
  • [ ] Examples appropriately added/updated.
  • [x] Unit tests added/updated.
  • [x] New/changed code adheres to DSC Community Style Guidelines.

FabienTschanz avatar Jun 17 '25 21:06 FabienTschanz

@Borgquite For visibility. Pinning module dependencies during DSC resource load is now working with this PR.

FabienTschanz avatar Jun 17 '25 21:06 FabienTschanz

@FabienTschanz Fantastic! If this works, I'm sure it will help a bunch of people out!

Borgquite avatar Jun 18 '25 10:06 Borgquite