source/Private/Invoke-GuestConfigurationPackage.ps1 (221 lines of code) (raw):

function Invoke-GuestConfigurationPackage { [CmdletBinding()] [OutputType([PSCustomObject])] param ( [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [System.IO.FileInfo] $Path, [Parameter()] [Hashtable[]] $Parameter = @(), [Parameter()] [Switch] $Apply ) $os = Get-OSPlatform if ($os -ieq 'MacOS') { throw 'This cmdlet is not supported on MacOS.' } #-----VALIDATE PARAMETERS----- $requiredParameterProperties = @('ResourceType', 'ResourceId', 'ResourcePropertyName', 'ResourcePropertyValue') foreach ($parameterInfo in $Parameter) { foreach ($requiredParameterProperty in $requiredParameterProperties) { if (-not ($parameterInfo.Keys -contains $requiredParameterProperty)) { $requiredParameterPropertyString = $requiredParameterProperties -join ', ' throw "One of the specified parameters is missing the mandatory property '$requiredParameterProperty'. The mandatory properties for parameters are: $requiredParameterPropertyString" } if ($parameterInfo[$requiredParameterProperty] -isnot [string]) { $requiredParameterPropertyString = $requiredParameterProperties -join ', ' throw "The property '$requiredParameterProperty' of one of the specified parameters is not a string. All parameter property values must be strings." } } } #-----VALIDATE PACKAGE SETUP----- $Path = Resolve-RelativePath -Path $Path if (-not (Test-Path -Path $Path -PathType 'Leaf')) { throw "No zip file found at the path '$Path'. Please specify the file path to a compressed Guest Configuration package (.zip) with the Path parameter." } $sourceZipFile = Get-Item -Path $Path if ($sourceZipFile.Extension -ine '.zip') { throw "The file found at the path '$Path' is not a .zip file. It has extension '$($sourceZipFile.Extension)'. Please specify the file path to a compressed Guest Configuration package (.zip) with the Path parameter." } # Install the Guest Configuration worker if needed Install-GCWorker # Extract the package $gcWorkerPath = Get-GCWorkerRootPath $gcWorkerPackagesFolderPath = Join-Path -Path $gcWorkerPath -ChildPath 'packages' $packageInstallFolderName = $sourceZipFile.BaseName $packageInstallPath = Join-Path -Path $gcWorkerPackagesFolderPath -ChildPath $packageInstallFolderName if (Test-Path -Path $packageInstallPath) { $null = Remove-Item -Path $packageInstallPath -Recurse -Force } $null = Expand-Archive -Path $Path -DestinationPath $packageInstallPath -Force # Find and validate the mof file $mofFilePattern = '*.mof' $mofChildItems = @( Get-ChildItem -Path $packageInstallPath -Filter $mofFilePattern -File ) if ($mofChildItems.Count -eq 0) { throw "No .mof file found in the package. The Guest Configuration package must include a compiled DSC configuration (.mof) with the same name as the package. Please use the New-GuestConfigurationPackage cmdlet to generate a valid package." } elseif ($mofChildItems.Count -gt 1) { throw "Found more than one .mof file in the extracted Guest Configuration package. Please remove any extra .mof files from the root of the package. Please use the New-GuestConfigurationPackage cmdlet to generate a valid package." } $mofFile = $mofChildItems[0] $packageName = $mofFile.BaseName # Get package version $metaconfigFileName = "{0}.metaconfig.json" -f $packageName $metaconfigFilePath = Join-Path -Path $packageInstallPath -ChildPath $metaconfigFileName if (Test-Path -Path $metaconfigFilePath) { $metaconfig = Get-Content -Path $metaconfigFilePath -Raw | ConvertFrom-Json | ConvertTo-OrderedHashtable if ($metaconfig.Keys -contains 'Version') { $packageVersion = $metaconfig['Version'] Write-Verbose -Message "Package has the version $packageVersion" } if ($metaconfig.Keys -contains 'Type') { $packageType = $metaconfig['Type'] Write-Verbose -Message "Package has the type $packageType" if ($packageType -eq 'Audit' -and $Apply) { throw 'The specified package has been marked as Audit-only. You cannot apply/remediate an Audit-only package. Please change the type of the package to "AuditAndSet" with New-GuestConfigurationPackage if you would like to apply/remediate with this package.' } } else { Write-Warning -Message "Failed to determine the package type from the metaconfig file '$metaconfigFileName' in the package. Please use the latest version of the New-GuestConfigurationPackage cmdlet to construct your package." } } else { Write-Warning -Message "Failed to find the metaconfig file '$metaconfigFileName' in the package. Please use the latest version of the New-GuestConfigurationPackage cmdlet to construct your package." } # Rename the package install folder to match what the GC worker expects if needed if ($packageName -ne $packageInstallFolderName) { $newPackageInstallPath = Join-Path -Path $gcWorkerPackagesFolderPath -ChildPath $packageName if (Test-Path -Path $newPackageInstallPath) { $null = Remove-Item -Path $newPackageInstallPath -Recurse -Force } $null = Rename-Item -Path $packageInstallPath -NewName $newPackageInstallPath $packageInstallPath = $newPackageInstallPath } $mofFilePath = Join-Path -Path $packageInstallPath -ChildPath $mofFile.Name # Validate dependencies $resourceDependencies = @( Get-MofResouceDependencies -MofFilePath $mofFilePath ) if ($resourceDependencies.Count -le 0) { throw "Failed to determine resource dependencies from the .mof file in the package. The Guest Configuration package must include a compiled DSC configuration (.mof) with the same name as the package. Please use the New-GuestConfigurationPackage cmdlet to generate a valid package." } $usingInSpecResource = $false $moduleDependencies = @() $inSpecProfileNames = @() $modulesFolderPath = Join-Path -Path $packageInstallPath -ChildPath 'Modules' $modulesFolders = @( Get-ChildItem -Path $modulesFolderPath -Directory ) if ($modulesFolders.Count -eq 0) { throw "There are no folders under the Modules folder in the package at '$modulesFolderPath'. Please use the New-GuestConfigurationPackage cmdlet to generate your package. If you wish to include custom native resources in the package, please copy the compiled files into the 'Modules/DscNativeResources/<resource_name>/' folder in the package manually. Example: <package_root>/Modules/DscNativeResources/MyNativeResource/libMyNativeResource.so AND <package_root>/Modules/DscNativeResources/MyNativeResource/MyNativeResource.schema.mof" } foreach ($resourceDependency in $resourceDependencies) { if ($resourceDependency['ResourceName'] -ieq 'MSFT_ChefInSpecResource') { $usingInSpecResource = $true $inSpecProfileNames += $resourceDependency['ResourceInstanceName'] continue } $getModuleDependenciesParameters = @{ ModuleName = $resourceDependency['ModuleName'] ModuleVersion = $resourceDependency['ModuleVersion'] ModuleSourcePath = $modulesFolderPath } $moduleDependencies += Get-ModuleDependencies @getModuleDependenciesParameters } if ($moduleDependencies.Count -gt 0) { Write-Verbose -Message "Found the module dependencies: $($moduleDependencies.Name)" } $duplicateModules = @( $moduleDependencies | Group-Object -Property 'Name' | Where-Object { $_.Count -gt 1 } ) foreach ($duplicateModule in $duplicateModules) { $uniqueVersions = @( $duplicateModule.Group.Version | Get-Unique ) if ($uniqueVersions.Count -gt 1) { $moduleName = $duplicateModule.Group[0].Name throw "Cannot include more than one version of a module in one package. Detected versions $uniqueVersions of the module '$moduleName' are needed for this package." } } if ($usingInSpecResource) { $metaConfigName = "$packageName.metaconfig.json" $metaConfigPath = Join-Path -Path $packageInstallPath -ChildPath $metaConfigName $metaConfigContent = Get-Content -Path $metaConfigPath -Raw $metaConfig = $metaConfigContent | ConvertFrom-Json $packageType = $metaConfig.Type if ($packageType -ieq 'AuditAndSet') { throw "The type of this package was specified as 'AuditAndSet', but native InSpec resource was detected in the provided .mof file. This resource does not currently support the set scenario and can only be used for 'Audit' packages." } Write-Verbose -Message "Expecting the InSpec profiles: $($inSpecProfileNames)" foreach ($expectedInSpecProfileName in $inSpecProfileNames) { $inSpecProfilePath = Join-Path -Path $modulesFolderPath -ChildPath $expectedInSpecProfileName $inSpecProfile = Get-Item -Path $inSpecProfilePath -ErrorAction 'SilentlyContinue' if ($null -eq $inSpecProfile) { throw "Expected to find an InSpec profile at the path '$inSpecProfilePath', but there is no item at this path." } elseif ($inSpecProfile -isnot [System.IO.DirectoryInfo]) { throw "Expected to find an InSpec profile at the path '$inSpecProfilePath', but the item at this path is not a directory." } else { $inSpecProfileYmlFileName = 'inspec.yml' $inSpecProfileYmlFilePath = Join-Path -Path $inSpecProfilePath -ChildPath $inSpecProfileYmlFileName if (-not (Test-Path -Path $inSpecProfileYmlFilePath -PathType 'Leaf')) { throw "Expected to find an InSpec profile at the path '$inSpecProfilePath', but there is no file named '$inSpecProfileYmlFileName' under this path." } } } } #-----RUN PACKAGE----- # Update package metaconfig to use debug mode and force module imports $metaconfigName = "$packageName.metaconfig.json" $metaconfigPath = Join-Path -Path $packageInstallPath -ChildPath $metaconfigName $propertiesToUpdate = @{ debugMode = 'ForceModuleImport' } if ($Apply) { $propertiesToUpdate['configurationMode'] = 'ApplyAndMonitor' } Set-GuestConfigurationPackageMetaconfigProperty -MetaconfigPath $metaconfigPath -Property $propertiesToUpdate # Update package configuration parameters if ($null -ne $Parameter -and $Parameter.Count -gt 0) { Set-GuestConfigurationPackageParameters -Path $mofFilePath -Parameter $Parameter } # Publish the package via GC worker Publish-GCWorkerAssignment -PackagePath $packageInstallPath # Set GC worker settings for the package Set-GCWorkerSettings -PackagePath $packageInstallPath # Invoke GC worker $result = Invoke-GCWorkerRun -ConfigurationName $packageName -Apply:$Apply return $result }