From be2b02dadb3912a4e639802dcb1d6d20c069adf4 Mon Sep 17 00:00:00 2001 From: Claudio Spizzi Date: Sun, 12 Sep 2021 01:21:25 +0200 Subject: [PATCH] Draft of the configuration and resource DSL --- .../Functions/DscExecConfiguration.ps1 | 39 +++++ PSDscExecutor/Functions/DscExecResource.ps1 | 148 ++++++++++++++++++ .../Helpers/Register-DscResourceFunction.ps1 | 51 ++++++ PSDscExecutor/PSDscExecutor.psd1 | 5 +- PSDscExecutor/PSDscExecutor.psm1 | 3 + 5 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 PSDscExecutor/Functions/DscExecConfiguration.ps1 create mode 100644 PSDscExecutor/Functions/DscExecResource.ps1 create mode 100644 PSDscExecutor/Helpers/Register-DscResourceFunction.ps1 diff --git a/PSDscExecutor/Functions/DscExecConfiguration.ps1 b/PSDscExecutor/Functions/DscExecConfiguration.ps1 new file mode 100644 index 0000000..3601320 --- /dev/null +++ b/PSDscExecutor/Functions/DscExecConfiguration.ps1 @@ -0,0 +1,39 @@ +<# + .SYNOPSIS + . + + .DESCRIPTION + . + + .INPUTS + . + + .OUTPUTS + . + + .EXAMPLE + PS C:\> DscExecConfiguration + . + + .LINK + https://github.com/claudiospizzi/PSDscExecutor +#> +function DscExecConfiguration +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true, Position = 0)] + [System.Management.Automation.ScriptBlock] + $Definition + ) + + try + { + & $Definition | Out-Null + } + catch + { + $PSCmdlet.ThrowTerminatingError($_) + } +} diff --git a/PSDscExecutor/Functions/DscExecResource.ps1 b/PSDscExecutor/Functions/DscExecResource.ps1 new file mode 100644 index 0000000..96cc70e --- /dev/null +++ b/PSDscExecutor/Functions/DscExecResource.ps1 @@ -0,0 +1,148 @@ +<# + .SYNOPSIS + . + + .DESCRIPTION + . + + .INPUTS + . + + .OUTPUTS + . + + .EXAMPLE + PS C:\> DscExecResource + . + + .LINK + https://github.com/claudiospizzi/PSDscExecutor +#> +function DscExecResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true, Position = 0)] + [System.String] + $ModuleName, + + [Parameter(Mandatory = $true, Position = 1)] + [System.String] + $ResourceName, + + [Parameter(Mandatory = $true, Position = 2)] + [System.String] + $Name, + + [Parameter(Mandatory = $true, Position = 3)] + [System.Collections.Hashtable] + $Property + ) + + try + { + if ($ModuleName.Contains('@')) + { + # The module definition contains the required version. Extract the + # version of the definition and the get the DSC resource. + $ModuleName, $moduleVersion = $ModuleName.Split('@', 2) + $dscResource = Get-DscResource -Module @{ ModuleName = $ModuleName; RequiredVersion = $moduleVersion } -Name $ResourceName + } + else + { + # Get the DSC resource right away. Check if the DSC resource was + # loaded and extract the version. + $dscResource = Get-DscResource -Module $ModuleName -Name $ResourceName + $moduleVersion = $dscResource.Version + if ($null -eq $moduleVersion) + { + $moduleVersion = 'n/a' + if ($dscResource.Path -like 'C:\Windows\System32\WindowsPowershell\v1.0\Modules\PSDesiredStateConfiguration\DscResources\*') + { + $moduleVersion = '1.1' + } + } + } + + # Verify all mandatory properties are part of the provided properties + # and extract them in a dedicated hash table. + $keyProperties = @{} + foreach ($propertyKey in $dscResource.Properties.Where({ $_.IsMandatory }).Name) + { + if ($Property.Keys -notcontains $propertyKey) + { + throw "The mandatory property $propertyKey is not specified in $ModuleName@$moduleVersion/$ResourceName/$Name." + } + $keyProperties[$propertyKey] = $Property[$propertyKey] + } + + # Copy and verify all provided properties. + $allProperties = @{} + foreach ($propertyKey in $Property.Keys) + { + if ($dscResource.Properties.Name -notcontains $propertyKey) + { + throw "The property $propertyKey not found in the resource $ModuleName@$moduleVersion/$ResourceName." + } + $allProperties[$propertyKey] = $Property[$propertyKey] + } + + # Prepare all the DSC invocation helpers + $invokeDscResourceVerbose = "[$ModuleName@$moduleVersion] [$ResourceName] [$Name]" + # $verboseResourceName = "[$ModuleName@$moduleVersion] [$ResourceName] [$Name]" + # $verboseResourceSpace = "[$ModuleName@$moduleVersion] [$ResourceName]" -replace '.', ' ' + # $verboseInstanceName = "$verboseResourceName [$Name]" + # $verboseInstanceSpace = $verboseInstanceName -replace '.', ' ' + $invokeDscResourceSplat = @{ + Name = $ResourceName + ModuleName = @{ + ModuleName = $ModuleName + RequiredVersion = $moduleVersion + } + } + + Write-Verbose $invokeDscResourceVerbose + + # Loop over the resource as long as it is not in the desired state. + $inDesiredState = $false + while (-not $inDesiredState) + { + Write-Verbose "$invokeDscResourceVerbose [Test] Start" + + $inDesiredState = Invoke-DscResource @invokeDscResourceSplat -Method 'Test' -Property $allProperties | Select-Object -ExpandProperty 'InDesiredState' + + Write-Verbose "$invokeDscResourceVerbose [Test] DesiredState = $inDesiredState" + + Write-Verbose "$invokeDscResourceVerbose [Test] End" + + if (-not $inDesiredState) + { + Write-Verbose "$invokeDscResourceVerbose [Set] Start" + + Invoke-DscResource @invokeDscResourceSplat -Method 'Set' -Property $allProperties + + Write-Verbose "$invokeDscResourceVerbose [Set] End" + } + } + + Write-Verbose "$invokeDscResourceVerbose [Get] Start" + + $stateData = Invoke-DscResource @invokeDscResourceSplat -Method 'Get' -Property $keyProperties + + foreach ($stateDataPropertyKey in $stateData.Keys) + { + Write-Verbose "$invokeDscResourceVerbose [Get] $stateDataPropertyKey = $($stateData[$stateDataPropertyKey])" + } + + Write-Verbose "$invokeDscResourceVerbose [Get] End" + + Write-Verbose $invokeDscResourceVerbose + + return $stateData + } + catch + { + $PSCmdlet.ThrowTerminatingError($_) + } +} diff --git a/PSDscExecutor/Helpers/Register-DscResourceFunction.ps1 b/PSDscExecutor/Helpers/Register-DscResourceFunction.ps1 new file mode 100644 index 0000000..8526661 --- /dev/null +++ b/PSDscExecutor/Helpers/Register-DscResourceFunction.ps1 @@ -0,0 +1,51 @@ +<# + .SYNOPSIS + . + + .DESCRIPTION + . + + .INPUTS + . + + .OUTPUTS + . + + .EXAMPLE + PS C:\> Register-DscResourceFunction + . + + .LINK + https://github.com/claudiospizzi/PSDscExecutor +#> +function Register-DscResourceFunction +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.PowerShell.DesiredStateConfiguration.DscResourceInfo] + $DscResourceInfo, + + [Parameter(Mandatory = $false)] + [ValidateSet('Script', 'Global')] + [System.String] + $Scope = 'Script' + ) + + begin + { + try + { + # Resource with no module name?!? + $functionName = '{0}:{1}@{2}\{3}' -f $Scope, $DscResourceInfo.ModuleName, $DscResourceInfo.Version, $DscResourceInfo.Name + $functionCode = [System.Management.Automation.ScriptBlock]::Create('param ($A) Write-Host "A: $A"') + + New-Item -Path 'Function:\' -Name $functionName -Value $functionCode | Out-Null + } + catch + { + $PSCmdlet.ThrowTerminatingError($_) + } + } +} diff --git a/PSDscExecutor/PSDscExecutor.psd1 b/PSDscExecutor/PSDscExecutor.psd1 index b147d71..244cf42 100644 --- a/PSDscExecutor/PSDscExecutor.psd1 +++ b/PSDscExecutor/PSDscExecutor.psd1 @@ -64,7 +64,10 @@ NestedModules = @() # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. - FunctionsToExport = @() + FunctionsToExport = @( + 'DscExecConfiguration' + 'DscExecResource' + ) # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @() diff --git a/PSDscExecutor/PSDscExecutor.psm1 b/PSDscExecutor/PSDscExecutor.psm1 index faac88b..84a726a 100644 --- a/PSDscExecutor/PSDscExecutor.psm1 +++ b/PSDscExecutor/PSDscExecutor.psm1 @@ -33,3 +33,6 @@ $Script:PSModuleVersion = (Import-PowerShellDataFile -Path "$Script:PSModulePath # Define module behaviour $ErrorActionPreference = 'Stop' Set-StrictMode -Version 'Latest' + +# Now, register all available DSC resource functions +# Get-DscResource | Register-DscResourceFunction -Scope 'Script'