From 224bbf2cd8ea5b5a4cc7392894d06655cf98695a Mon Sep 17 00:00:00 2001
From: Andrew <anmenaga@microsoft.com>
Date: Thu, 23 May 2024 22:51:55 -0700
Subject: [PATCH 1/5] Stage 1

---
 .../psDscAdapter/powershell.resource.ps1      |  11 +-
 .../psDscAdapter/psDscAdapter.psm1            | 114 +++++++++++++++++-
 2 files changed, 120 insertions(+), 5 deletions(-)

diff --git a/powershell-adapter/psDscAdapter/powershell.resource.ps1 b/powershell-adapter/psDscAdapter/powershell.resource.ps1
index 2cb8d99c..7d2a6a38 100644
--- a/powershell-adapter/psDscAdapter/powershell.resource.ps1
+++ b/powershell-adapter/psDscAdapter/powershell.resource.ps1
@@ -28,7 +28,9 @@ if ('Validate' -ne $Operation) {
 }
 
 if ($jsonInput) {
-    $inputobj_pscustomobj = $jsonInput | ConvertFrom-Json
+    if ($jsonInput -ne '@{}') {
+        $inputobj_pscustomobj = $jsonInput | ConvertFrom-Json
+    }
     $new_psmodulepath = $inputobj_pscustomobj.psmodulepath
     if ($new_psmodulepath)
     {
@@ -39,10 +41,11 @@ if ($jsonInput) {
 # process the operation requested to the script
 switch ($Operation) {
     'List' {
-        $dscResourceCache = Invoke-DscCacheRefresh
+        Invoke-DscCacheRefresh
+        #$dscResourceCache = Invoke-DscCacheRefresh
 
         # cache was refreshed on script load
-        foreach ($dscResource in $dscResourceCache) {
+        <#foreach ($dscResource in $dscResourceCache) {
         
             # https://learn.microsoft.com/dotnet/api/system.management.automation.dscresourceinfo
             $DscResourceInfo = $dscResource.DscResourceInfo
@@ -92,7 +95,7 @@ switch ($Operation) {
                 requireAdapter = $requireAdapter
                 description    = $description
             } | ConvertTo-Json -Compress
-        }
+        }#>
     }
     { @('Get','Set','Test','Export') -contains $_ } {
         $desiredState = $psDscAdapter.invoke(   { param($jsonInput) Get-DscResourceObject -jsonInput $jsonInput }, $jsonInput )
diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1
index 6bd234f2..9785d870 100644
--- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1
+++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1
@@ -20,6 +20,118 @@ function Import-PSDSCModule {
     $PSDesiredStateConfiguration = Import-Module $m -Force -PassThru
 }
 
+function Get-DSCResourceModules
+{
+    $listPSModuleFolders = $env:PSModulePath.Split([IO.Path]::PathSeparator)
+    $dscModulePsd1List = [System.Collections.Generic.HashSet[System.String]]::new()
+    foreach ($folder in $listPSModuleFolders)
+    {
+        if (!(Test-Path $folder))
+        {
+            continue
+        }
+
+        foreach($moduleFolder in Get-ChildItem $folder -Directory)
+        {
+            $addModule = $false
+            foreach($psd1 in Get-ChildItem -Recurse -Filter "$($moduleFolder.Name).psd1" -Path $moduleFolder.fullname -Depth 2)
+            {
+                $containsDSCResource = select-string -LiteralPath $psd1 -pattern '^[^#]*\bDscResourcesToExport\b.*'
+                if($null -ne $containsDSCResource)
+                {
+                    $dscModulePsd1List.Add($psd1) | Out-Null
+                    break
+                }
+            }
+        }
+    }
+    
+    return $dscModulePsd1List
+}
+
+function FindAndParseResourceDefinitions
+{
+    [CmdletBinding(HelpUri = '')]
+    param(
+        [Parameter(Mandatory = $true)]
+        [string]$filePath
+    )
+
+    if (-not (Test-Path $filePath))
+    {
+        return
+    }
+
+    if (([System.IO.Path]::GetExtension($filePath) -ne ".psm1") -and ([System.IO.Path]::GetExtension($filePath) -ne ".ps1"))
+    {
+        return
+    }
+
+    $filePath
+        
+    [System.Management.Automation.Language.Token[]] $tokens = $null
+    [System.Management.Automation.Language.ParseError[]] $errors = $null
+    $ast = [System.Management.Automation.Language.Parser]::ParseFile($filePath, [ref]$tokens, [ref]$errors)
+    $resourceDefinitions = $ast.FindAll(
+        {
+            $typeAst = $args[0] -as [System.Management.Automation.Language.TypeDefinitionAst]
+            if ($typeAst)
+            {
+                foreach($a in $typeAst.Attributes)
+                {
+                    if ($a.TypeName.Name -eq 'DscResource')
+                    {
+                        return $true;
+                    }
+                }
+            }
+
+            return $false;
+        },
+        $false);
+
+    $resourceDefinitions.Name
+}
+
+function LoadPowerShellClassResourcesFromModule
+{
+    [CmdletBinding(HelpUri = '')]
+    param(
+        [Parameter(Mandatory = $true)]
+        [PSModuleInfo]$moduleInfo
+    )
+
+    if ($moduleInfo.RootModule)
+    {
+        $scriptPath = Join-Path $moduleInfo.ModuleBase  $moduleInfo.RootModule
+    }
+    else
+    {
+        $scriptPath = $moduleInfo.Path;
+    }
+
+    FindAndParseResourceDefinitions $scriptPath
+
+    if ($moduleInfo.NestedModules)
+    {
+        foreach ($nestedModule in $moduleInfo.NestedModules)
+        {
+            LoadPowerShellClassResourcesFromModule $nestedModule
+        }
+    }
+}
+
+function Invoke-DscCacheRefresh {
+
+    $dscResourceModulePsd1s = Get-DSCResourceModules
+    if($null -ne $dscResourceModulePsd1s) {
+        $modules = Get-Module -ListAvailable -Name ($dscResourceModulePsd1s)
+        foreach ($mod in $modules)
+        {
+            LoadPowerShellClassResourcesFromModule -moduleInfo $mod
+        }
+    }
+}
 <# public function Invoke-DscCacheRefresh
 .SYNOPSIS
     This function caches the results of the Get-DscResource call to optimize performance.
@@ -32,7 +144,7 @@ function Import-PSDSCModule {
 .EXAMPLE
     Invoke-DscCacheRefresh -Module "PSDesiredStateConfiguration"
 #>
-function Invoke-DscCacheRefresh {
+function Invoke-DscCacheRefresh_ORIGINAL {
     [CmdletBinding(HelpUri = '')]
     param(
         [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]

From d557c6d1b75431cf2e87b4d5a40f0c9f43ee3af1 Mon Sep 17 00:00:00 2001
From: Andrew <anmenaga@microsoft.com>
Date: Fri, 24 May 2024 12:03:26 -0700
Subject: [PATCH 2/5] Stage 2

---
 .../Tests/powershellgroup.resource.tests.ps1  |   3 +-
 .../psDscAdapter/powershell.resource.ps1      |   9 +-
 .../psDscAdapter/psDscAdapter.psm1            | 138 +++++++++++-------
 3 files changed, 92 insertions(+), 58 deletions(-)

diff --git a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1
index 5dd3dcb1..ba791e2f 100644
--- a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1
+++ b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1
@@ -23,13 +23,12 @@ Describe 'PowerShell adapter resource tests' {
         Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath
     }
 
-    It 'Discovery includes class-based and script-based resources ' -Skip:(!$IsWindows){
+    It 'Discovery includes class-based resources' -Skip:(!$IsWindows){
 
         $r = dsc resource list * -a Microsoft.DSC/PowerShell
         $LASTEXITCODE | Should -Be 0
         $resources = $r | ConvertFrom-Json
         ($resources | ? {$_.Type -eq 'TestClassResource/TestClassResource'}).Count | Should -Be 1
-        ($resources | ? {$_.Type -eq 'PSTestModule/TestPSRepository'}).Count | Should -Be 1
     }
 
     It 'Get works on class-based resource' -Skip:(!$IsWindows){
diff --git a/powershell-adapter/psDscAdapter/powershell.resource.ps1 b/powershell-adapter/psDscAdapter/powershell.resource.ps1
index 7d2a6a38..53b3d327 100644
--- a/powershell-adapter/psDscAdapter/powershell.resource.ps1
+++ b/powershell-adapter/psDscAdapter/powershell.resource.ps1
@@ -41,11 +41,10 @@ if ($jsonInput) {
 # process the operation requested to the script
 switch ($Operation) {
     'List' {
-        Invoke-DscCacheRefresh
-        #$dscResourceCache = Invoke-DscCacheRefresh
+        $dscResourceCache = Invoke-DscCacheRefresh
 
         # cache was refreshed on script load
-        <#foreach ($dscResource in $dscResourceCache) {
+        foreach ($dscResource in $dscResourceCache) {
         
             # https://learn.microsoft.com/dotnet/api/system.management.automation.dscresourceinfo
             $DscResourceInfo = $dscResource.DscResourceInfo
@@ -85,7 +84,7 @@ switch ($Operation) {
             [resourceOutput]@{
                 type           = $dscResource.Type
                 kind           = 'Resource'
-                version        = $DscResourceInfo.version.ToString()
+                version        = [string]$DscResourceInfo.version
                 capabilities   = $capabilities
                 path           = $DscResourceInfo.Path
                 directory      = $DscResourceInfo.ParentPath
@@ -95,7 +94,7 @@ switch ($Operation) {
                 requireAdapter = $requireAdapter
                 description    = $description
             } | ConvertTo-Json -Compress
-        }#>
+        }
     }
     { @('Get','Set','Test','Export') -contains $_ } {
         $desiredState = $psDscAdapter.invoke(   { param($jsonInput) Get-DscResourceObject -jsonInput $jsonInput }, $jsonInput )
diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1
index 9785d870..6c64090a 100644
--- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1
+++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1
@@ -66,9 +66,10 @@ function FindAndParseResourceDefinitions
     {
         return
     }
-
-    $filePath
-        
+    
+    "Loading resources from '$filePath'" | Write-DscTrace -Operation Trace
+    #TODO: Handle class inheritance 
+    #TODO: Ensure embedded instances in properties are working correctly
     [System.Management.Automation.Language.Token[]] $tokens = $null
     [System.Management.Automation.Language.ParseError[]] $errors = $null
     $ast = [System.Management.Automation.Language.Parser]::ParseFile($filePath, [ref]$tokens, [ref]$errors)
@@ -90,7 +91,61 @@ function FindAndParseResourceDefinitions
         },
         $false);
 
-    $resourceDefinitions.Name
+    $resourceList = [System.Collections.Generic.List[DscResourceInfo]]::new()
+
+    foreach($typeDefinitionAst in $resourceDefinitions)
+    {
+        $DscResourceInfo = [DscResourceInfo]::new()
+        $DscResourceInfo.Name = $typeDefinitionAst.Name
+        $DscResourceInfo.ResourceType = $typeDefinitionAst.Name
+        $DscResourceInfo.FriendlyName = $typeDefinitionAst.Name
+        $DscResourceInfo.ImplementationDetail = 'ClassBased'
+        $DscResourceInfo.Module = $filePath
+        $DscResourceInfo.Path = $filePath
+        #TODO: ModuleName, Version and ParentPath should be taken from psd1 contents
+        $DscResourceInfo.ModuleName = [System.IO.Path]::GetFileNameWithoutExtension($filePath) 
+        $DscResourceInfo.ParentPath = [System.IO.Path]::GetDirectoryName($filePath)
+
+        $DscResourceInfo.Properties = [System.Collections.Generic.List[DscResourcePropertyInfo]]::new()
+        foreach ($member in $typeDefinitionAst.Members)
+        {
+            $property = $member -as [System.Management.Automation.Language.PropertyMemberAst]
+            if (($property -eq $null) -or ($property.IsStatic))
+            {
+                continue;
+            }
+            $skipProperty = $true
+            $isKeyProperty = $false
+            foreach($attr in $property.Attributes)
+            {
+                if ($attr.TypeName.Name -eq 'DscProperty')
+                {
+                    $skipProperty = $false
+                    foreach($attrArg in $attr.NamedArguments)
+                    {
+                        if ($attrArg.ArgumentName -eq 'Key')
+                        {
+                            $isKeyProperty = $true
+                        }
+                    }
+                }
+            }
+            if ($skipProperty)
+            {
+                continue;
+            }
+
+            [DscResourcePropertyInfo]$prop = [DscResourcePropertyInfo]::new()
+            $prop.Name = $property.Name
+            $prop.PropertyType = $property.PropertyType.TypeName.Name
+            $prop.IsMandatory = $isKeyProperty 
+            $DscResourceInfo.Properties.Add($prop)
+        }
+        
+        $resourceList.Add($DscResourceInfo)
+    }
+
+    return $resourceList
 }
 
 function LoadPowerShellClassResourcesFromModule
@@ -110,28 +165,20 @@ function LoadPowerShellClassResourcesFromModule
         $scriptPath = $moduleInfo.Path;
     }
 
-    FindAndParseResourceDefinitions $scriptPath
+    $Resources = FindAndParseResourceDefinitions $scriptPath
 
     if ($moduleInfo.NestedModules)
     {
         foreach ($nestedModule in $moduleInfo.NestedModules)
         {
-            LoadPowerShellClassResourcesFromModule $nestedModule
+            $resourcesOfNestedModules = LoadPowerShellClassResourcesFromModule $nestedModule
+            $Resources.AddRange($resourcesOfNestedModules)
         }
     }
-}
 
-function Invoke-DscCacheRefresh {
-
-    $dscResourceModulePsd1s = Get-DSCResourceModules
-    if($null -ne $dscResourceModulePsd1s) {
-        $modules = Get-Module -ListAvailable -Name ($dscResourceModulePsd1s)
-        foreach ($mod in $modules)
-        {
-            LoadPowerShellClassResourcesFromModule -moduleInfo $mod
-        }
-    }
+    return $Resources
 }
+
 <# public function Invoke-DscCacheRefresh
 .SYNOPSIS
     This function caches the results of the Get-DscResource call to optimize performance.
@@ -144,7 +191,7 @@ function Invoke-DscCacheRefresh {
 .EXAMPLE
     Invoke-DscCacheRefresh -Module "PSDesiredStateConfiguration"
 #>
-function Invoke-DscCacheRefresh_ORIGINAL {
+function Invoke-DscCacheRefresh {
     [CmdletBinding(HelpUri = '')]
     param(
         [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
@@ -158,12 +205,8 @@ function Invoke-DscCacheRefresh_ORIGINAL {
         # PS 6+ on Windows
         Join-Path $env:LocalAppData "dsc\PSAdapterCache.json"
     } else {
-        # either WinPS or PS 6+ on Linux/Mac
-        if ($PSVersionTable.PSVersion.Major -le 5) {
-            Join-Path $env:LocalAppData "dsc\WindowsPSAdapterCache.json"
-        } else {
-            Join-Path $env:HOME ".dsc" "PSAdapterCache.json"
-        }
+        # PS 6+ on Linux/Mac
+        Join-Path $env:HOME ".dsc" "PSAdapterCache.json"
     }
 
     if (Test-Path $cacheFilePath) {
@@ -225,33 +268,18 @@ function Invoke-DscCacheRefresh_ORIGINAL {
         # create a list object to store cache of Get-DscResource
         [dscResourceCacheEntry[]]$dscResourceCacheEntries = [System.Collections.Generic.List[Object]]::new()
 
-        Import-PSDSCModule
-        $DscResources = Get-DscResource
-
-        foreach ($dscResource in $DscResources) {
-            # resources that shipped in Windows should only be used with Windows PowerShell
-            if ($dscResource.ParentPath -like "$env:windir\System32\*" -and $PSVersionTable.PSVersion.Major -gt 5) {
-                continue
-            }
-
-            if ( $dscResource.ImplementationDetail ) {
-                # only support known dscResourceType
-                if ([dscResourceType].GetEnumNames() -notcontains $dscResource.ImplementationDetail) {
-                    'WARNING: implementation detail not found: ' + $dscResource.ImplementationDetail | Write-DscTrace
-                    continue
-                }
-            }
-
-            $DscResourceInfo = [DscResourceInfo]::new()
-            $dscResource.PSObject.Properties | ForEach-Object -Process {
-                if ($null -ne $_.Value) {
-                    $DscResourceInfo.$($_.Name) = $_.Value
-                }
-                else {
-                    $DscResourceInfo.$($_.Name) = ''
-                }
+        $DscResources = [System.Collections.Generic.List[DscResourceInfo]]::new()
+        $dscResourceModulePsd1s = Get-DSCResourceModules
+        if($null -ne $dscResourceModulePsd1s) {
+            $modules = Get-Module -ListAvailable -Name ($dscResourceModulePsd1s)
+            foreach ($mod in $modules)
+            {
+                [System.Collections.Generic.List[DscResourceInfo]]$r = LoadPowerShellClassResourcesFromModule -moduleInfo $mod
+                $DscResources.AddRange($r)
             }
+        }
 
+        foreach ($dscResource in $DscResources) {
             $moduleName = $dscResource.ModuleName
 
             # fill in resource files (and their last-write-times) that will be used for up-do-date checks
@@ -262,7 +290,7 @@ function Invoke-DscCacheRefresh_ORIGINAL {
 
             $dscResourceCacheEntries += [dscResourceCacheEntry]@{
                 Type            = "$moduleName/$($dscResource.Name)"
-                DscResourceInfo = $DscResourceInfo
+                DscResourceInfo = $dscResource
                 LastWriteTimes = $lastWriteTimes
             }
         }
@@ -454,6 +482,14 @@ enum dscResourceType {
     Composite
 }
 
+class DscResourcePropertyInfo
+{
+    [string] $Name
+    [string] $PropertyType
+    [bool] $IsMandatory
+    [System.Collections.Generic.List[string]] $Values
+}
+
 # dsc resource type (settable clone)
 class DscResourceInfo {
     [dscResourceType] $ImplementationDetail
@@ -467,5 +503,5 @@ class DscResourceInfo {
     [string] $ParentPath
     [string] $ImplementedAs
     [string] $CompanyName
-    [psobject[]] $Properties
+    [System.Collections.Generic.List[DscResourcePropertyInfo]] $Properties
 }

From e07e5bdf9cdb0adbd86466fd1f1a0579d533b684 Mon Sep 17 00:00:00 2001
From: Andrew <anmenaga@microsoft.com>
Date: Fri, 24 May 2024 12:47:26 -0700
Subject: [PATCH 3/5] Stage 3

---
 .../Tests/powershellgroup.config.tests.ps1    | 14 ++++-----
 .../Tests/powershellgroup.resource.tests.ps1  | 14 ++++-----
 .../psDscAdapter/powershell.resource.ps1      | 31 ++++++++++++-------
 .../psDscAdapter/psDscAdapter.psm1            |  5 +++
 4 files changed, 38 insertions(+), 26 deletions(-)

diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1
index 269b6d4e..2f4d5499 100644
--- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1
+++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1
@@ -24,7 +24,7 @@ Describe 'PowerShell adapter resource tests' {
         Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath
     }
 
-    It 'Get works on config with class-based resources' -Skip:(!$IsWindows){
+    It 'Get works on config with class-based resources' {
 
         $r = Get-Content -Raw $pwshConfigPath | dsc config get
         $LASTEXITCODE | Should -Be 0
@@ -33,7 +33,7 @@ Describe 'PowerShell adapter resource tests' {
         $res.results[0].result.actualState.result[0].properties.EnumProp | Should -BeExactly 'Expected'
     }
     
-    It 'Test works on config with class-based resources' -Skip:(!$IsWindows){
+    It 'Test works on config with class-based resources' {
 
         $r = Get-Content -Raw $pwshConfigPath | dsc config test
         $LASTEXITCODE | Should -Be 0
@@ -41,7 +41,7 @@ Describe 'PowerShell adapter resource tests' {
         $res.results[0].result.actualState.result[0] | Should -Not -BeNull
     }
 
-    It 'Set works on config with class-based resources' -Skip:(!$IsWindows){
+    It 'Set works on config with class-based resources' {
 
         $r = Get-Content -Raw $pwshConfigPath | dsc config set
         $LASTEXITCODE | Should -Be 0
@@ -49,7 +49,7 @@ Describe 'PowerShell adapter resource tests' {
         $res.results.result.afterState.result[0].type | Should -Be "TestClassResource/TestClassResource"
     }
 
-    It 'Export works on config with class-based resources' -Skip:(!$IsWindows){
+    It 'Export works on config with class-based resources' {
 
         $yaml = @'
             $schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
@@ -71,7 +71,7 @@ Describe 'PowerShell adapter resource tests' {
         $res.resources[0].properties.result[0].Prop1 | Should -Be "Property of object1"
     }
 
-    It 'Custom psmodulepath in config works' -Skip:(!$IsWindows){
+    It 'Custom psmodulepath in config works' {
 
         $OldPSModulePath  = $env:PSModulePath
         Copy-Item -Recurse -Force -Path "$PSScriptRoot/TestClassResource" -Destination $TestDrive
@@ -104,7 +104,7 @@ Describe 'PowerShell adapter resource tests' {
         }
     }
 
-    It 'DSCConfigRoot macro is working when config is from a file' -Skip:(!$IsWindows){
+    It 'DSCConfigRoot macro is working when config is from a file' {
 
         $yaml = @"
             `$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
@@ -129,7 +129,7 @@ Describe 'PowerShell adapter resource tests' {
         $res.results.result.actualState.result.properties.Prop1 | Should -Be $TestDrive
     }
 
-    It 'DSC_CONFIG_ROOT env var is cwd when config is piped from stdin' -Skip:(!$IsWindows){
+    It 'DSC_CONFIG_ROOT env var is cwd when config is piped from stdin' {
 
         $yaml = @"
             `$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
diff --git a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1
index ba791e2f..b189842e 100644
--- a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1
+++ b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1
@@ -23,7 +23,7 @@ Describe 'PowerShell adapter resource tests' {
         Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath
     }
 
-    It 'Discovery includes class-based resources' -Skip:(!$IsWindows){
+    It 'Discovery includes class-based resources' {
 
         $r = dsc resource list * -a Microsoft.DSC/PowerShell
         $LASTEXITCODE | Should -Be 0
@@ -31,7 +31,7 @@ Describe 'PowerShell adapter resource tests' {
         ($resources | ? {$_.Type -eq 'TestClassResource/TestClassResource'}).Count | Should -Be 1
     }
 
-    It 'Get works on class-based resource' -Skip:(!$IsWindows){
+    It 'Get works on class-based resource' {
 
         $r = "{'Name':'TestClassResource1'}" | dsc resource get -r 'TestClassResource/TestClassResource'
         $LASTEXITCODE | Should -Be 0
@@ -39,7 +39,7 @@ Describe 'PowerShell adapter resource tests' {
         $res.actualState.result.properties.Prop1 | Should -BeExactly 'ValueForProp1'
     }
 
-    It 'Get uses enum names on class-based resource' -Skip:(!$IsWindows){
+    It 'Get uses enum names on class-based resource' {
 
         $r = "{'Name':'TestClassResource1'}" | dsc resource get -r 'TestClassResource/TestClassResource'
         $LASTEXITCODE | Should -Be 0
@@ -47,7 +47,7 @@ Describe 'PowerShell adapter resource tests' {
         $res.actualState.result.properties.EnumProp | Should -BeExactly 'Expected'
     }
 
-    It 'Test works on class-based resource' -Skip:(!$IsWindows){
+    It 'Test works on class-based resource' {
 
         $r = "{'Name':'TestClassResource1','Prop1':'ValueForProp1'}" | dsc resource test -r 'TestClassResource/TestClassResource'
         $LASTEXITCODE | Should -Be 0
@@ -55,7 +55,7 @@ Describe 'PowerShell adapter resource tests' {
         $res.actualState.result.properties.InDesiredState | Should -Be $True
     }
 
-    It 'Set works on class-based resource' -Skip:(!$IsWindows){
+    It 'Set works on class-based resource' {
 
         $r = "{'Name':'TestClassResource1','Prop1':'ValueForProp1'}" | dsc resource set -r 'TestClassResource/TestClassResource'
         $LASTEXITCODE | Should -Be 0
@@ -63,7 +63,7 @@ Describe 'PowerShell adapter resource tests' {
         $res.afterState.result | Should -Not -BeNull
     }
 
-    It 'Export works on PS class-based resource' -Skip:(!$IsWindows){
+    It 'Export works on PS class-based resource' {
 
         $r = dsc resource export -r TestClassResource/TestClassResource
         $LASTEXITCODE | Should -Be 0
@@ -73,7 +73,7 @@ Describe 'PowerShell adapter resource tests' {
         $res.resources[0].properties.result[0].Prop1 | Should -Be "Property of object1"
     }
 
-    It 'Get --all works on PS class-based resource' -Skip:(!$IsWindows){
+    It 'Get --all works on PS class-based resource' {
 
         $r = dsc resource get --all -r TestClassResource/TestClassResource
         $LASTEXITCODE | Should -Be 0
diff --git a/powershell-adapter/psDscAdapter/powershell.resource.ps1 b/powershell-adapter/psDscAdapter/powershell.resource.ps1
index 53b3d327..6a9f6bce 100644
--- a/powershell-adapter/psDscAdapter/powershell.resource.ps1
+++ b/powershell-adapter/psDscAdapter/powershell.resource.ps1
@@ -9,6 +9,24 @@ param(
     [string]$jsonInput = '@{}'
 )
 
+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 = $Message } | ConvertTo-Json -Compress
+    $host.ui.WriteErrorLine($trace)
+}
+
+# Adding some debug info to STDERR
+'PSVersion=' + $PSVersionTable.PSVersion.ToString() | Write-DscTrace
+'PSPath=' + $PSHome | Write-DscTrace
+'PSModulePath=' + $env:PSModulePath | Write-DscTrace
+
 if ('Validate' -ne $Operation) {
     # write $jsonInput to STDERR for debugging
     $trace = @{'Debug' = 'jsonInput=' + $jsonInput } | ConvertTo-Json -Compress
@@ -21,7 +39,6 @@ if ('Validate' -ne $Operation) {
     else {
         $psDscAdapter = Import-Module "$PSScriptRoot/psDscAdapter.psd1" -Force -PassThru
     }
-    
 
     # initialize OUTPUT as array
     $result = [System.Collections.Generic.List[Object]]::new()
@@ -50,6 +67,7 @@ switch ($Operation) {
             $DscResourceInfo = $dscResource.DscResourceInfo
 
             # Provide a way for existing resources to specify their capabilities, or default to Get, Set, Test
+            # TODO: for perf, it is better to take capabilities from psd1 in Invoke-DscCacheRefresh, not by extra call to Get-Module
             if ($DscResourceInfo.ModuleName) {
                 $module = Get-Module -Name $DscResourceInfo.ModuleName -ListAvailable | Sort-Object -Property Version -Descending | Select-Object -First 1
                 if ($module.PrivateData.PSData.DscCapabilities) {
@@ -161,14 +179,3 @@ class resourceOutput {
     [string] $requireAdapter
     [string] $description
 }
-
-# Adding some debug info to STDERR
-$trace = @{'Debug' = 'PSVersion=' + $PSVersionTable.PSVersion.ToString() } | ConvertTo-Json -Compress
-$host.ui.WriteErrorLine($trace)
-$trace = @{'Debug' = 'PSPath=' + $PSHome } | ConvertTo-Json -Compress
-$host.ui.WriteErrorLine($trace)
-$m = Get-Command 'Get-DscResource'
-$trace = @{'Debug' = 'Module=' + $m.Source.ToString() } | ConvertTo-Json -Compress
-$host.ui.WriteErrorLine($trace)
-$trace = @{'Debug' = 'PSModulePath=' + $env:PSModulePath } | ConvertTo-Json -Compress
-$host.ui.WriteErrorLine($trace)
\ No newline at end of file
diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1
index 6c64090a..a4e5824e 100644
--- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1
+++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1
@@ -73,6 +73,11 @@ function FindAndParseResourceDefinitions
     [System.Management.Automation.Language.Token[]] $tokens = $null
     [System.Management.Automation.Language.ParseError[]] $errors = $null
     $ast = [System.Management.Automation.Language.Parser]::ParseFile($filePath, [ref]$tokens, [ref]$errors)
+    foreach($e in $errors)
+    {
+        $e | Out-String | Write-DscTrace -Operation Error
+    }
+
     $resourceDefinitions = $ast.FindAll(
         {
             $typeAst = $args[0] -as [System.Management.Automation.Language.TypeDefinitionAst]

From 50d3e86fe301c84a70ed100254de7405016f770f Mon Sep 17 00:00:00 2001
From: Andrew <anmenaga@microsoft.com>
Date: Fri, 24 May 2024 13:41:44 -0700
Subject: [PATCH 4/5] Stage 4

---
 build.ps1 | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/build.ps1 b/build.ps1
index 4e5ba9c5..a69cb635 100644
--- a/build.ps1
+++ b/build.ps1
@@ -324,10 +324,13 @@ if (!$Clippy -and !$SkipBuild) {
 if ($Test) {
     $failed = $false
 
-    $FullyQualifiedName = @{ModuleName="PSDesiredStateConfiguration";ModuleVersion="2.0.7"}
-    if (-not(Get-Module -ListAvailable -FullyQualifiedName $FullyQualifiedName))
-    {   "Installing module PSDesiredStateConfiguration 2.0.7"
-        Install-PSResource -Name PSDesiredStateConfiguration -Version 2.0.7 -Repository PSGallery -TrustRepository
+    if ($IsWindows) {
+        # PSDesiredStateConfiguration module is needed for Microsoft.Windows/WindowsPowerShell adapter
+        $FullyQualifiedName = @{ModuleName="PSDesiredStateConfiguration";ModuleVersion="2.0.7"}
+        if (-not(Get-Module -ListAvailable -FullyQualifiedName $FullyQualifiedName))
+        {   "Installing module PSDesiredStateConfiguration 2.0.7"
+            Install-PSResource -Name PSDesiredStateConfiguration -Version 2.0.7 -Repository PSGallery -TrustRepository
+        }
     }
 
     if (-not(Get-Module -ListAvailable -Name Pester))

From cb17f138e65cb85d35caca3c9c573c6faebec466 Mon Sep 17 00:00:00 2001
From: Andrew <anmenaga@microsoft.com>
Date: Fri, 24 May 2024 14:09:37 -0700
Subject: [PATCH 5/5] Stage 5

---
 powershell-adapter/Tests/powershellgroup.config.tests.ps1   | 3 ++-
 powershell-adapter/Tests/powershellgroup.resource.tests.ps1 | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1
index 2f4d5499..cd152d69 100644
--- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1
+++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1
@@ -78,13 +78,14 @@ Describe 'PowerShell adapter resource tests' {
         Rename-Item -Path "$PSScriptRoot/TestClassResource" -NewName "_TestClassResource"
 
         try {
+            $psmp = "`$env:PSModulePath"+[System.IO.Path]::PathSeparator+$TestDrive
             $yaml = @"
             `$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json
             resources:
             - name: Working with class-based resources
               type: Microsoft.DSC/PowerShell
               properties:
-                psmodulepath: `$env:PSModulePath;$TestDrive
+                psmodulepath: $psmp
                 resources:
                 - name: Class-resource Info
                   type: TestClassResource/TestClassResource
diff --git a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1
index b189842e..edf83910 100644
--- a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1
+++ b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1
@@ -25,7 +25,7 @@ Describe 'PowerShell adapter resource tests' {
 
     It 'Discovery includes class-based resources' {
 
-        $r = dsc resource list * -a Microsoft.DSC/PowerShell
+        $r = dsc resource list '*' -a Microsoft.DSC/PowerShell
         $LASTEXITCODE | Should -Be 0
         $resources = $r | ConvertFrom-Json
         ($resources | ? {$_.Type -eq 'TestClassResource/TestClassResource'}).Count | Should -Be 1