microsoft / PSRule

Validate infrastructure as code (IaC) and objects using PowerShell rules.
https://microsoft.github.io/PSRule/v2/
MIT License
395 stars 49 forks source link

A Rule do not find an enum generated by Powershell ( native class version 5.1 ) but found a class generated by Add-Type. #188

Closed LaurentDardenne closed 5 years ago

LaurentDardenne commented 5 years ago

Description of the issue A Rule do not find an enum generated by Powershell ( native class version 5.1 ) but found a class generated by Add-Type.

First case, we create an Enum with the 'enum' powershell keyword :

$Path='C:\temp'

$Rule='Test.Rule.ps1'
@'
Rule test {
 try {
    #[System.Enum]::Parse([MyActions],$TargetObject,$true) > $null
    [System.AppDomain]::CurrentDomain.GetAssemblies()|
      Where-object {$_.location -eq $null}|
      Foreach-object { 
          Write-Warning "Assembly : $($_.Fullname)";
          $_.DefinedTypes|
           % {Write-warning $_} 
      }
    [MyActions] > $null      
    $true
 }
 catch {
     Write-Warning "Exception inside Rule: `r`n$($_|select * |out-string)"
     $false
 }
}
'@ >"$Path\$Rule"

$Script='CallRule.ps1'
@"
 'Delete'|Invoke-PSRule -path "$Path\$Rule" 
"@ >"$Path\$Script"

enum MyActions{
    Delete
    Add
}
cd $path
&"$Path\$Script"

# AVERTISSEMENT : Assembly : Anonymously Hosted DynamicMethods Assembly, Version=0.0.0.0, Culture=neutral,
# PublicKeyToken=null
# AVERTISSEMENT : Assembly : PSEventHandler, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
# AVERTISSEMENT : Assembly : ComSnippets, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
# AVERTISSEMENT : Type$ConvertByrefToPtr, ComSnippets, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
# AVERTISSEMENT : Assembly : System.Management.Automation.ComInterop.DynamicAssembly, Version=0.0.0.0, Culture=neutral,
# PublicKeyToken=null
# AVERTISSEMENT : Assembly : ⧹powershell, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
# AVERTISSEMENT : MyActions
# AVERTISSEMENT : Exception inside Rule:

# PSMessageDetails      :
# Exception             : System.Management.Automation.RuntimeException: Type [MyActions] introuvable.
#                            à System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext
#                         funcContext, Exception exception)
#                            à System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame
#                         frame)
#                            à
#                         System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame
#                         frame)
#                            à
#                         System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame
#                         frame)
# TargetObject          : MyActions
# CategoryInfo          : InvalidOperation : (MyActions:TypeName) [], RuntimeException
# FullyQualifiedErrorId : TypeNotFound
# ErrorDetails          :
# InvocationInfo        : System.Management.Automation.InvocationInfo
# ScriptStackTrace      : à <ScriptBlock>, C:\temp\Test.Rule.ps1 : ligne 11
# PipelineIterationInfo : {}

#    TargetName : 5b863ffbc007f3c294c6739f6359359cfd3db547

# RuleName                            Outcome    Recommendation
# --------                            -------    --------------
# test                                Fail

Access to the [Actions] class throws an exception, although the dynamic assembly exists.

Second case, we create an Enum with the 'Add-Type' cmdlet inside a new session. We use the previous scripts :

$code=@'
public enum MyActions{
     Delete,
     Add,
};
'@
add-type $code
$Path='C:\temp'
cd $path
$Script='CallRule.ps1'
&"$Path\$Script"

# AVERTISSEMENT : Assembly : Anonymously Hosted DynamicMethods Assembly, Version=0.0.0.0, Culture=neutral,
# PublicKeyToken=null
# AVERTISSEMENT : Assembly : PSEventHandler, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
# AVERTISSEMENT : Assembly : ComSnippets, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
# AVERTISSEMENT : Type$ConvertByrefToPtr, ComSnippets, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
# AVERTISSEMENT : Assembly : System.Management.Automation.ComInterop.DynamicAssembly, Version=0.0.0.0, Culture=neutral,
# PublicKeyToken=null

#    TargetName : 5b863ffbc007f3c294c6739f6359359cfd3db547

# RuleName                            Outcome    Recommendation
# --------                            -------    --------------
# test                                Pass

In this case the code inside the rule find the class. I don't know if it is an issue with scoping or with the runtime Powershell , may be the type of assembly is different and is problematic...

BernieWhite commented 5 years ago

@LaurentDardenne Hmm not sure. Good question. I think you are correct it would be related to scoping and PowerShell handling of the app domain. It requires further investigation.

BernieWhite commented 5 years ago

@LaurentDardenne I've looked into this a little more.

Actually the enum is there, it's just being not found by the type resolver. As far as I can tell it's intentional, to work around the issue of unloading types from PowerShell and to limit cross runspace issues.

Interesting that the Add-Type has a different result, but I suspect it was to preserve existing behavior.

We can even create an instance of the enum, just not using [MyAction].

$Path='C:\temp'
$Rule='Test.Rule.ps1'
@'

Rule test {
 try {
    #[System.Enum]::Parse([MyActions],$TargetObject,$true) > $null
    [System.AppDomain]::CurrentDomain.GetAssemblies()|
      Where-object {$_.location -eq $null}|
      Foreach-object { 
          Write-Warning "Assembly : $($_.Fullname)";
          $_.DefinedTypes|
           % {Write-warning $_} 

           $_.DefinedTypes|
           % { $_::new() -ne $Null }
      }
    #[MyActions] > $null      
    $true
 }
 catch {
     Write-Warning "Exception inside Rule: `r`n$($_|select * |out-string)"
     $false
 }
}
'@ >"$Path\$Rule"

$Script='CallRule.ps1'
@"
 'Delete'|Invoke-PSRule -path "$Path\$Rule" 
"@ >"$Path\$Script"

enum MyActions{
    Delete
    Add
}
cd $path
&"$Path\$Script"

If you want the enum to be accessed by PSRule, add the enum to the same .Rule.ps1 file or a peer file like Common.Rule.ps1 that gets loaded by Invoke-PSRule -Path .\.

BernieWhite commented 5 years ago

Hope that answers your question?

LaurentDardenne commented 5 years ago

Hope that answers your question?

Yes, it specifies the behavior and the way of doing things. Thank you.