diff --git a/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psd1 b/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psd1 index c852f0a10..ec4d3a17e 100644 --- a/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psd1 +++ b/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psd1 @@ -34,7 +34,7 @@ VariablesToExport = @() AliasesToExport = @() # DSC resources to export from this module -DscResourcesToExport = @('TestClassResource', 'NoExport') +DscResourcesToExport = @('TestClassResource', 'NoExport', 'FilteredExport') # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. PrivateData = @{ diff --git a/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psm1 b/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psm1 index 4e0808c8e..ae0a79253 100644 --- a/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psm1 +++ b/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psm1 @@ -155,6 +155,52 @@ class NoExport: BaseTestClass } } +[DscResource()] +class FilteredExport : BaseTestClass +{ + [DscProperty(Key)] + [string] $Name + + [DscProperty()] + [string] $Prop1 + + [void] Set() + { + } + + [bool] Test() + { + return $true + } + + [FilteredExport] Get() + { + return $this + } + + static [FilteredExport[]] Export() + { + $resultList = [List[FilteredExport]]::new() + $obj = New-Object FilteredExport + $obj.Name = "DefaultObject" + $obj.Prop1 = "Default Property" + $resultList.Add($obj) + + return $resultList.ToArray() + } + + static [FilteredExport[]] Export([FilteredExport]$Name) + { + $resultList = [List[FilteredExport]]::new() + $obj = New-Object FilteredExport + $obj.Name = $Name + $obj.Prop1 = "Filtered Property for $Name" + $resultList.Add($obj) + + return $resultList.ToArray() + } +} + function Test-World() { "Hello world from PSTestModule!" diff --git a/adapters/powershell/Tests/powershellgroup.config.tests.ps1 b/adapters/powershell/Tests/powershellgroup.config.tests.ps1 index 3ebee5844..09df8968f 100644 --- a/adapters/powershell/Tests/powershellgroup.config.tests.ps1 +++ b/adapters/powershell/Tests/powershellgroup.config.tests.ps1 @@ -105,6 +105,49 @@ Describe 'PowerShell adapter resource tests' { $out | Should -BeLike "*ERROR*Export method not implemented by resource 'TestClassResource/NoExport'*" } + It 'Export works with filtered export property' { + $yaml = @' + $schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Working with class-based resources + type: Microsoft.DSC/PowerShell + properties: + resources: + - name: Class-resource Info + type: TestClassResource/FilteredExport + properties: + Name: 'FilteredExport' +'@ + $out = $yaml | dsc -l trace config export -f - 2> "$TestDrive/export_trace.txt" + $LASTEXITCODE | Should -Be 0 + $res = $out | ConvertFrom-Json + $res.'$schema' | Should -BeExactly 'https://aka.ms/dsc/schemas/v3/bundled/config/document.json' + $res.'resources' | Should -Not -BeNullOrEmpty + $res.resources[0].properties.result.count | Should -Be 1 + $res.resources[0].properties.result[0].Name | Should -Be "FilteredExport" + $res.resources[0].properties.result[0].Prop1 | Should -Be "Filtered Property for FilteredExport" + "$TestDrive/export_trace.txt" | Should -FileContentMatch "Properties provided for filtered export" + } + + It 'Export fails when filtered export is requested but not implemented' { + $yaml = @' + $schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Working with class-based resources + type: Microsoft.DSC/PowerShell + properties: + resources: + - name: Class-resource Info + type: TestClassResource/NoExport + properties: + Name: 'SomeFilter' +'@ + $out = $yaml | dsc config export -f - 2>&1 | Out-String + $LASTEXITCODE | Should -Be 2 + $out | Should -Not -BeNullOrEmpty + $out | Should -BeLike "*ERROR*Export method with parameters not implemented by resource 'TestClassResource/NoExport'*" + } + It 'Custom psmodulepath in config works' { $OldPSModulePath = $env:PSModulePath diff --git a/adapters/powershell/psDscAdapter/powershell.resource.ps1 b/adapters/powershell/psDscAdapter/powershell.resource.ps1 index fd4d116d6..aa314da5d 100644 --- a/adapters/powershell/psDscAdapter/powershell.resource.ps1 +++ b/adapters/powershell/psDscAdapter/powershell.resource.ps1 @@ -181,7 +181,7 @@ switch ($Operation) { # process the INPUT (desiredState) for each resource as dscresourceInfo and return the OUTPUT as actualState $actualState = $psDscAdapter.invoke( { param($op, $ds, $dscResourceCache) Invoke-DscOperation -Operation $op -DesiredState $ds -dscResourceCache $dscResourceCache }, $Operation, $ds, $dscResourceCache) if ($null -eq $actualState) { - Write-DscTrace -Operation Error -Message 'Incomplete GET for resource ' + $ds.Name + "Failed to invoke operation '{0}' for resource name '{1}'." -f $Operation, $ds.Name | Write-DscTrace -Operation Error exit 1 } if ($null -ne $actualState.Properties -and $actualState.Properties.InDesiredState -eq $false) { diff --git a/adapters/powershell/psDscAdapter/psDscAdapter.psm1 b/adapters/powershell/psDscAdapter/psDscAdapter.psm1 index 5f1542c84..1c22fb0b5 100644 --- a/adapters/powershell/psDscAdapter/psDscAdapter.psm1 +++ b/adapters/powershell/psDscAdapter/psDscAdapter.psm1 @@ -150,6 +150,42 @@ function FindAndParseResourceDefinitions { return $resourceList } +function GetExportMethod ($ResourceType, $HasFilterProperties, $ResourceTypeName) { + $methods = $ResourceType.GetMethods() | Where-Object { $_.Name -eq 'Export' } + $method = $null + + if ($HasFilterProperties) { + "Properties provided for filtered export" | Write-DscTrace -Operation Trace + $method = foreach ($mt in $methods) { + if ($mt.GetParameters().Count -gt 0) { + $mt + break + } + } + + if ($null -eq $method) { + "Export method with parameters not implemented by resource '$ResourceTypeName'. Filtered export is not supported." | Write-DscTrace -Operation Error + exit 1 + } + } + else { + "No properties provided, using parameterless export" | Write-DscTrace -Operation Trace + $method = foreach ($mt in $methods) { + if ($mt.GetParameters().Count -eq 0) { + $mt + break + } + } + + if ($null -eq $method) { + "Export method not implemented by resource '$ResourceTypeName'" | Write-DscTrace -Operation Error + exit 1 + } + } + + return $method +} + function LoadPowerShellClassResourcesFromModule { [CmdletBinding(HelpUri = '')] param( @@ -471,20 +507,18 @@ function Invoke-DscOperation { } 'Export' { $t = $dscResourceInstance.GetType() - $methods = $t.GetMethods() | Where-Object { $_.Name -eq 'Export' } - $method = foreach ($mt in $methods) { - if ($mt.GetParameters().Count -eq 0) { - $mt - break - } - } + $hasFilter = $null -ne $DesiredState.properties -and + ($DesiredState.properties.PSObject.Properties | Measure-Object).Count -gt 0 + + $method = GetExportMethod -ResourceType $t -HasFilterProperties $hasFilter -ResourceTypeName $DesiredState.Type - if ($null -eq $method) { - "Export method not implemented by resource '$($DesiredState.Type)'" | Write-DscTrace -Operation Error - exit 1 - } $resultArray = @() - $raw_obj_array = $method.Invoke($null, $null) + if ($hasFilter) { + $raw_obj_array = $method.Invoke($null, @($dscResourceInstance)) + } else { + $raw_obj_array = $method.Invoke($null, $null) + } + foreach ($raw_obj in $raw_obj_array) { $Result_obj = @{} $ValidProperties | ForEach-Object {