diff --git a/build.ps1 b/build.ps1 index 5cb6b3922..063af8294 100755 --- a/build.ps1 +++ b/build.ps1 @@ -73,6 +73,7 @@ $filesForWindowsPackage = @( 'sshdconfig.exe', 'sshd-windows.dsc.resource.json', 'sshd_config.dsc.resource.json', + 'windowspowershell.dsc.extension.json', 'windowspowershell.dsc.resource.json', 'wmi.dsc.resource.json', 'wmi.resource.ps1', @@ -86,7 +87,7 @@ $filesForLinuxPackage = @( 'bicep.dsc.extension.json', 'dsc', 'dsc_default.settings.json', - 'dsc.settings.json' + 'dsc.settings.json', 'dscecho', 'echo.dsc.resource.json', 'assertion.dsc.resource.json', @@ -111,7 +112,7 @@ $filesForMacPackage = @( 'bicep.dsc.extension.json', 'dsc', 'dsc_default.settings.json', - 'dsc.settings.json' + 'dsc.settings.json', 'dscecho', 'echo.dsc.resource.json', 'assertion.dsc.resource.json', @@ -316,6 +317,7 @@ if (!$SkipBuild) { "dsc", "dscecho", "extensions/bicep", + "extensions/powershell" "osinfo", "powershell-adapter", 'resources/PSScript', diff --git a/dsc/examples/variable.dsc.ps1 b/dsc/examples/variable.dsc.ps1 new file mode 100644 index 000000000..e76d89230 --- /dev/null +++ b/dsc/examples/variable.dsc.ps1 @@ -0,0 +1,13 @@ +configuration VariableConfiguration { + Import-DscResource -ModuleName PSDesiredStateConfiguration + Node localhost + { + Environment PathEnvironmentVariable { + Name = 'TestPathEnvironmentVariable' + Value = 'TestValue' + Ensure = 'Present' + Path = $true + Target = @('Process') + } + } +} \ No newline at end of file diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index d37455465..59a5f46fc 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -23,30 +23,28 @@ Describe 'Discover extension tests' { It 'Discover extensions' { $out = dsc extension list | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - if ($IsWindows) { - $out.Count | Should -Be 3 -Because ($out | Out-String) - $out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep' - $out[0].version | Should -Be '0.1.0' - $out[0].capabilities | Should -BeExactly @('import') - $out[0].manifest | Should -Not -BeNullOrEmpty - $out[1].type | Should -Be 'Microsoft.Windows.Appx/Discover' - $out[1].version | Should -Be '0.1.0' - $out[1].capabilities | Should -BeExactly @('discover') - $out[1].manifest | Should -Not -BeNullOrEmpty - $out[2].type | Should -BeExactly 'Test/Discover' - $out[2].version | Should -BeExactly '0.1.0' - $out[2].capabilities | Should -BeExactly @('discover') - $out[2].manifest | Should -Not -BeNullOrEmpty + $expectedExtensions = if ($IsWindows) { + @( + @{ type = 'Microsoft.DSC.Extension/Bicep'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.DSC.Transitional/PSDesiredStateConfiguration'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.Windows.Appx/Discover'; version = '0.1.0'; capabilities = @('discover') } + @{ type = 'Test/Discover'; version = '0.1.0'; capabilities = @('discover') } + ) } else { - $out.Count | Should -Be 2 -Because ($out | Out-String) - $out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep' - $out[0].version | Should -Be '0.1.0' - $out[0].capabilities | Should -BeExactly @('import') - $out[0].manifest | Should -Not -BeNullOrEmpty - $out[1].type | Should -BeExactly 'Test/Discover' - $out[1].version | Should -BeExactly '0.1.0' - $out[1].capabilities | Should -BeExactly @('discover') - $out[1].manifest | Should -Not -BeNullOrEmpty + @( + @{ type = 'Microsoft.DSC.Extension/Bicep'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Test/Discover'; version = '0.1.0'; capabilities = @('discover') } + ) + } + + $out.Count | Should -Be $expectedExtensions.Count -Because ($out | Out-String) + + foreach ($expected in $expectedExtensions) { + $extension = $out | Where-Object { $_.type -eq $expected.type } + $extension | Should -Not -BeNullOrEmpty -Because "Extension $($expected.type) should exist" + $extension.version | Should -BeExactly $expected.version + $extension.capabilities | Should -BeExactly $expected.capabilities + $extension.manifest | Should -Not -BeNullOrEmpty } } diff --git a/extensions/powershell/convert-resource.ps1 b/extensions/powershell/convert-resource.ps1 new file mode 100644 index 000000000..36b76e925 --- /dev/null +++ b/extensions/powershell/convert-resource.ps1 @@ -0,0 +1,40 @@ +[CmdletBinding()] +param ( + [Parameter(ValueFromPipeline = $true)] + [string[]]$stringInput +) + +begin { + $lines = [System.Collections.Generic.List[string]]::new() + + if ($PSVersionTable.PSEdition -ne 'Core') { + # Remove all PowerShell paths + $env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { + $_ -notmatch 'PowerShell[\\/]7' -and + $_ -notmatch 'Program Files[\\/]PowerShell[\\/]' -and + $_ -notmatch 'Documents[\\/]PowerShell[\\/]' + }) -join ';' + + # Make sure the default path is Windows PowerShell is included + $winPsPath = "$env:windir\System32\WindowsPowerShell\v1.0\Modules" + if ($env:PSModulePath -notmatch [regex]::Escape($winPsPath)) { + $env:PSModulePath = $env:PSModulePath + [System.IO.Path]::PathSeparator + $winPsPath + } + } + + $scriptModule = Import-Module "$PSScriptRoot/convertDscResource.psd1" -Force -PassThru -WarningAction SilentlyContinue -ErrorAction Stop +} + +process { + foreach ($line in $stringInput) { + $lines.Add($line) + } +} + +end { + if ($lines.Count -ne 0) { + $result = $scriptModule.invoke( { param($lines) Build-DscConfigDocument -Content $lines }, ($lines | Out-String) ) + + return ($result | ConvertTo-Json -Depth 10 -Compress) + } +} \ No newline at end of file diff --git a/extensions/powershell/convertDscResource.psd1 b/extensions/powershell/convertDscResource.psd1 new file mode 100644 index 000000000..bb3aebf9b --- /dev/null +++ b/extensions/powershell/convertDscResource.psd1 @@ -0,0 +1,47 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +@{ + + # Script module or binary module file associated with this manifest. + RootModule = 'convertDscResource.psm1' + + # Version number of this module. + moduleVersion = '0.0.1' + + # ID used to uniquely identify this module + GUID = '95f93fd3-34ff-417e-80e4-f0112918a0bd' + + # Author of this module + Author = 'Microsoft Corporation' + + # Company or vendor of this module + CompanyName = 'Microsoft Corporation' + + # Copyright statement for this module + Copyright = '(c) Microsoft Corporation. All rights reserved.' + + # Description of the functionality provided by this module + Description = 'PowerShell Desired State Configuration Module for converting DSC Resources to JSON format' + + # 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 = @( + 'Write-DscTrace' + 'Build-DscConfigDocument' + ) + + # 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 = @() + + # Variables to export from this module + VariablesToExport = @() + + # Aliases 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 aliases to export. + AliasesToExport = @() + + PrivateData = @{ + PSData = @{ + ProjectUri = 'https://github.com/PowerShell/DSC' + } + } +} diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 new file mode 100644 index 000000000..ef82ba2d3 --- /dev/null +++ b/extensions/powershell/convertDscResource.psm1 @@ -0,0 +1,326 @@ +$Script:IsPowerShellCore = $PSVersionTable.PSEdition -eq 'Core' + +if ($Script:IsPowerShellCore) { + if ($IsWindows) { + Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -UseWindowsPowerShell -WarningAction SilentlyContinue -ErrorAction Stop + } + Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' -WarningAction SilentlyContinue -ErrorAction Stop +} else { + Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -WarningAction SilentlyContinue -ErrorAction Stop +} + +function Write-DscTrace { + param( + [Parameter(Mandatory = $false)] + [ValidateSet('Error', 'Warn', 'Info', 'Debug', 'Trace')] + [string]$Operation = 'Debug', + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [string]$Message + ) + + $trace = @{$Operation.ToLower() = $Message } | ConvertTo-Json -Compress + $host.ui.WriteErrorLine($trace) +} + +function Build-DscConfigDocument { + [CmdletBinding()] + [OutputType([System.Collections.Specialized.OrderedDictionary])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Content + ) + + # declare configuration document + $configurationDocument = [ordered]@{ + "`$schema" = "https://aka.ms/dsc/schemas/v3/bundled/config/document.json" + resources = @() + } + + # convert object to hashtable(s) + $dscObjects = ConvertTo-DscObject @PSBoundParameters -ErrorAction SilentlyContinue + + if (-not $dscObjects -or $dscObjects.Count -eq 0) { + "No DSC objects found in the provided content." | Write-DscTrace -Operation Error + exit 1 + } + + # store all resources in variables + $resources = [System.Collections.Generic.List[object]]::new() + + foreach ($dscObject in $dscObjects) { + $resource = [PSCustomObject]@{ + name = $dscObject.ResourceInstanceName + type = ("{0}/{1}" -f $dscObject.ModuleName, $dscObject.ResourceName) + properties = @() + } + + $properties = [ordered]@{} + + foreach ($dscObjectProperty in $dscObject.GetEnumerator()) { + if ($dscObjectProperty.Key -notin @('ResourceInstanceName', 'ResourceName', 'ModuleName', 'DependsOn', 'ConfigurationName', 'Type')) { + $properties.Add($dscObjectProperty.Key, $dscObjectProperty.Value) + } + } + + # add properties + $resource.properties = $properties + + if ($dscObject.ContainsKey('DependsOn') -and $dscObject.DependsOn) { + $dependsOnKeys = $dscObject.DependsOn.Split("]").Replace("[", "") + + $previousGroupHash = $dscObjects | Where-Object { $_.ResourceName -eq $dependsOnKeys[0] -and $_.ResourceInstanceName -eq $dependsOnKeys[1] } + if ($previousGroupHash) { + $dependsOnString = "[resourceId('$("{0}/{1}" -f $previousGroupHash.ModuleName, $previousGroupHash.ResourceName)','$($previousGroupHash.ResourceInstanceName)')]" + + # add it to the object + $resource | Add-Member -MemberType NoteProperty -Name 'dependsOn' -Value @($dependsOnString) + } + } + + $resources.Add($resource) + } + + $configurationDocument.resources = $resources + + return $configurationDocument +} + +function ConvertTo-DscObject { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Content + ) + + $result = @() + $Tokens = $null + $ParseErrors = $null + + # Remove the module version information. + $start = $Content.ToLower().IndexOf('import-dscresource') + if ($start -ge 0) { + $end = $Content.IndexOf("`n", $start) + if ($end -gt $start) { + $start = $Content.ToLower().IndexOf("-moduleversion", $start) + if ($start -ge 0 -and $start -lt $end) { + $Content = $Content.Remove($start, $end - $start) + } + } + } + + # Rename the configuration node to ensure a valid name is used. + $start = $Content.ToLower().IndexOf("`nconfiguration") + if ($start -lt 0) { + $start = $Content.ToLower().IndexOf(' configuration ') + } + if ($start -ge 0) { + $end = $Content.IndexOf("`n", $start) + if ($end -gt $start) { + $start = $Content.ToLower().IndexOf(' ', $start + 1) + if ($start -ge 0 -and $start -lt $end) { + $Content = $Content.Remove($start, $end - $start) + $Content = $Content.Insert($start, " TempDSCParserConfiguration") + } + } + } + + $AST = [System.Management.Automation.Language.Parser]::ParseInput($Content, [ref]$Tokens, [ref]$ParseErrors) + + # Look up the Configuration definition ("") + $Config = $AST.Find({ $Args[0].GetType().Name -eq 'ConfigurationDefinitionAst' }, $False) + + # Retrieve information about the DSC Modules imported in the config + # and get the list of their associated resources. + $ModulesToLoad = @() + foreach ($statement in $config.body.ScriptBlock.EndBlock.Statements) { + if ($null -ne $statement.CommandElements -and $null -ne $statement.CommandElements[0].Value -and ` + $statement.CommandElements[0].Value -eq 'Import-DSCResource') { + $currentModule = @{} + for ($i = 0; $i -le $statement.CommandElements.Count; $i++) { + if ($statement.CommandElements[$i].ParameterName -eq 'ModuleName' -and ` + ($i + 1) -lt $statement.CommandElements.Count) { + $moduleName = $statement.CommandElements[$i + 1].Value + $currentModule.Add('ModuleName', $moduleName) + } elseif ($statement.CommandElements[$i].ParameterName -eq 'Module' -and ` + ($i + 1) -lt $statement.CommandElements.Count) { + $moduleName = $statement.CommandElements[$i + 1].Value + $currentModule.Add('ModuleName', $moduleName) + } elseif ($statement.CommandElements[$i].ParameterName -eq 'ModuleVersion' -and ` + ($i + 1) -lt $statement.CommandElements.Count) { + $moduleVersion = $statement.CommandElements[$i + 1].Value + $currentModule.Add('ModuleVersion', $moduleVersion) + } + } + $ModulesToLoad += $currentModule + } + } + $DSCResources = @() + foreach ($moduleToLoad in $ModulesToLoad) { + $loadedModuleTest = Get-Module -Name $moduleToLoad.ModuleName -ListAvailable | Where-Object -FilterScript { $_.Version -eq $moduleToLoad.ModuleVersion } + + if ($null -eq $loadedModuleTest -and -not [System.String]::IsNullOrEmpty($moduleToLoad.ModuleVersion)) { + "Module {$($moduleToLoad.ModuleName)} version {$($moduleToLoad.ModuleVersion)} specified in the configuration isn't installed on the machine/agent. Install it by running: Install-Module -Name '$($moduleToLoad.ModuleName)' -RequiredVersion '$($moduleToLoad.ModuleVersion)'" | Write-DscTrace -Operation Error + exit 1 + } else { + if ($Script:IsPowerShellCore) { + $currentResources = Get-PwshDscResource -Module $moduleToLoad.ModuleName + } else { + $currentResources = Get-DSCResource -Module $moduleToLoad.ModuleName + } + + if (-not [System.String]::IsNullOrEmpty($moduleToLoad.ModuleVersion)) { + $currentResources = $currentResources | Where-Object -FilterScript { $_.Version -eq $moduleToLoad.ModuleVersion } + } + $DSCResources += $currentResources + } + } + + if ($DSCResources.Count -eq 0) { + "No DSC resources found in the imported modules." | Write-DscTrace -Operation Error + exit 1 + } + + # Drill down + # Body.ScriptBlock is the part after "Configuration {" + # EndBlock is the actual code within that Configuration block + # Find the first DynamicKeywordStatement that has a word "Node" in it, find all "NamedBlockAst" elements, these are the DSC resource definitions + try { + $resourceInstances = $Config.Body.ScriptBlock.EndBlock.Statements.Find({ $Args[0].GetType().Name -eq 'DynamicKeywordStatementAst' -and $Args[0].CommandElements[0].StringConstantType -eq 'BareWord' -and $Args[0].CommandElements[0].Value -eq 'Node' }, $False).commandElements[2].ScriptBlock.Find({ $Args[0].GetType().Name -eq 'NamedBlockAst' }, $False).Statements + } catch { + $resourceInstances = $Config.Body.ScriptBlock.EndBlock.Statements | Where-Object -FilterScript { $null -ne $_.CommandElements -and $_.CommandElements[0].Value -ne 'Import-DscResource' } + } + + # Get the name of the configuration. + $configurationName = $Config.InstanceName.Value + + $totalCount = 1 + foreach ($resource in $resourceInstances) { + $currentResourceInfo = @{} + + # CommandElements + # 0 - Resource Type + # 1 - Resource Instance Name + # 2 - Key/Pair Value list of parameters. + $resourceType = $resource.CommandElements[0].Value + $resourceInstanceName = $resource.CommandElements[1].Value + + $percent = ($totalCount / ($resourceInstances.Count) * 100) + Write-Progress -Status "[$totalCount/$($resourceInstances.Count)] $resourceType - $resourceInstanceName" ` + -PercentComplete $percent ` + -Activity "Parsing Resources" + $currentResourceInfo.Add("ResourceName", $resourceType) + $currentResourceInfo.Add("ResourceInstanceName", $resourceInstanceName) + $currentResourceInfo.Add("ModuleName", $ModulesToLoad.ModuleName) + $currentResourceInfo.Add("ConfigurationName", $configurationName) + + # Get a reference to the current resource. + $currentResource = $DSCResources | Where-Object -FilterScript { $_.Name -eq $resourceType } + + # Loop through all the key/pair value + foreach ($keyValuePair in $resource.CommandElements[2].KeyValuePairs) { + $isVariable = $false + $key = $keyValuePair.Item1.Value + + if ($null -ne $keyValuePair.Item2.PipelineElements) { + if ($null -eq $keyValuePair.Item2.PipelineElements.Expression.Value) { + if ($null -ne $keyValuePair.Item2.PipelineElements.Expression) { + if ($keyValuePair.Item2.PipelineElements.Expression.StaticType.Name -eq 'Object[]') { + $value = $keyValuePair.Item2.PipelineElements.Expression.SubExpression + $newValue = @() + foreach ($expression in $value.Statements.PipelineElements.Expression) { + if ($null -ne $expression.Elements) { + foreach ($element in $expression.Elements) { + if ($null -ne $element.VariablePath) { + $newValue += "`$" + $element.VariablePath.ToString() + } elseif ($null -ne $element.Value) { + $newValue += $element.Value + } + } + } else { + $newValue += $expression.Value + } + } + $value = $newValue + } else { + $value = $keyValuePair.Item2.PipelineElements.Expression.ToString() + } + } else { + $value = $keyValuePair.Item2.PipelineElements.Parent.ToString() + } + + if ($value.GetType().Name -eq 'String' -and $value.StartsWith('$')) { + $isVariable = $true + } + } else { + $value = $keyValuePair.Item2.PipelineElements.Expression.Value + } + } + + # Retrieve the current property's type based on the resource's schema. + $currentPropertyInResourceSchema = $currentResource.Properties | Where-Object -FilterScript { $_.Name -eq $key } + $valueType = $currentPropertyInResourceSchema.PropertyType + + # If the value type is null, then the parameter doesn't exist + # in the resource's schema and we throw a warning + $propertyFound = $true + if ($null -eq $valueType) { + $propertyFound = $false + } + + if ($propertyFound) { + # If the current property is not a CIMInstance + if (-not $valueType.StartsWith('[MSFT_') -and ` + $valueType -ne '[string]' -and ` + $valueType -ne '[string[]]' -and ` + -not $isVariable) { + # Try to parse the value based on the retrieved type. + $scriptBlock = @" + `$typeStaticMethods = $valueType | gm -static + if (`$typeStaticMethods.Name.Contains('TryParse')) + { + $valueType::TryParse(`$value, [ref]`$value) | Out-Null + } +"@ + Invoke-Expression -Command $scriptBlock | Out-Null + } elseif ($valueType -eq '[String]' -or $isVariable) { + if ($isVariable -and [Boolean]::TryParse($value.TrimStart('$'), [ref][Boolean])) { + if ($value -eq "`$true") { + $value = $true + } else { + $value = $false + } + } else { + $value = $value + } + } elseif ($valueType -eq '[string[]]') { + # If the property is an array but there's only one value + # specified as a string (not specifying the @()) then + # we need to create the array. + if ($value.GetType().Name -eq 'String' -and -not $value.StartsWith('@(')) { + $value = @($value) + } + } else { + $isArray = $false + if ($keyValuePair.Item2.ToString().StartsWith('@(')) { + $isArray = $true + } + if ($isArray) { + $value = @($value) + } + } + $currentResourceInfo.Add($key, $value) | Out-Null + } + } + + $result += $currentResourceInfo + $totalCount++ + } + Write-Progress -Completed ` + -Activity "Parsing Resources" + + return [System.Array]$result +} \ No newline at end of file diff --git a/extensions/powershell/copy_files.txt b/extensions/powershell/copy_files.txt new file mode 100644 index 000000000..9ab85b46b --- /dev/null +++ b/extensions/powershell/copy_files.txt @@ -0,0 +1,4 @@ +windowspowershell.dsc.extension.json +convert-resource.ps1 +convertDscResource.psd1 +convertDscResource.psm1 \ No newline at end of file diff --git a/extensions/powershell/win_powershell.tests.ps1 b/extensions/powershell/win_powershell.tests.ps1 new file mode 100644 index 000000000..a9a886491 --- /dev/null +++ b/extensions/powershell/win_powershell.tests.ps1 @@ -0,0 +1,43 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +BeforeDiscovery { + if ($IsWindows) { + $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [System.Security.Principal.WindowsPrincipal]::new($identity) + $isElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) + } +} + +Describe 'PowerShell extension tests' { + It 'Example PowerShell file should work' -Skip:(!$IsWindows -or !$isElevated) { + $psFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\variable.dsc.ps1" + $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) + $out.results[0].result.actualState.Ensure | Should -Be 'Absent' + $psFile = $psFile.ToString().Replace('\', '\\') + (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Transitional/PSDesiredStateConfiguration'" + } + + It 'Invalid PowerShell configuration document file returns error' -Skip:(!$IsWindows) { + $psFile = "$TestDrive/invalid.ps1" + Set-Content -Path $psFile -Value @" +configuration InvalidConfiguration { + Import-DscResource -ModuleName InvalidModule + Node localhost + { + Test Invalid { + Name = 'InvalidTest' + Ensure = 'Present' + } + } +} +"@ + dsc -l trace config get -f $psFile 2>$TestDrive/error.log + $LASTEXITCODE | Should -Be 2 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) + $content = (Get-Content -Path $TestDrive/error.log -Raw) + $psFile = $psFile.ToString().Replace('\', '\\') + $content | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Transitional/PSDesiredStateConfiguration'" + $content | Should -Match "No DSC resources found in the imported modules." + } +} diff --git a/extensions/powershell/windowspowershell.dsc.extension.json b/extensions/powershell/windowspowershell.dsc.extension.json new file mode 100644 index 000000000..d4b35fd61 --- /dev/null +++ b/extensions/powershell/windowspowershell.dsc.extension.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Microsoft.DSC.Transitional/PSDesiredStateConfiguration", + "version": "0.1.0", + "description": "Enable passing Windows PowerShell v1 configuration document file directly to DSC. Works only on Windows and leverages the built-in PSDesiredStateConfiguration module.", + "import": { + "fileExtensions": ["ps1"], + "executable": "powershell", + "args": [ + "-NoLogo", + "-NonInteractive", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "Get-Content", + { + "fileArg": "" + }, + "| ./convert-resource.ps1" + ] + } +} diff --git a/powershell-adapter/Tests/win_powershell_cache.tests.ps1 b/powershell-adapter/Tests/win_powershell_cache.tests.ps1 index 8f2ca4b58..6ed7bec1c 100644 --- a/powershell-adapter/Tests/win_powershell_cache.tests.ps1 +++ b/powershell-adapter/Tests/win_powershell_cache.tests.ps1 @@ -48,7 +48,7 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio } It 'Get works on Binary "File" resource' { - + $testFile = "$testdrive\test.txt" 'test' | Set-Content -Path $testFile -Force $r = '{"DestinationPath":"' + $testFile.replace('\', '\\') + '"}' | dsc resource get -r 'PSDesiredStateConfiguration/File' -f -