Skip to content
This repository has been archived by the owner on Jun 14, 2024. It is now read-only.

Set-StrictMode behavior breaks valid Configurations from (x)PSDesiredStateConfiguration #188

Open
begna112 opened this issue May 7, 2020 · 3 comments
Labels
discussion The issue is a discussion.

Comments

@begna112
Copy link

begna112 commented May 7, 2020

TLDR: PSDscResources should disable StrictMode in all of its Resources and optionally create parameters in them to enable this setting.

Details of the scenario you tried and the problem that is occurring

I recently needed to write some new Configurations and discovered that PSDscResources was the successor to PSDesiredStateConfiguration and xPSDesiredStateConfiguration. I wrote the new Configurations with PSDscResources and updated one or two (but not all) others to use it as well.

I deployed these changes to a test server (let's call this Host1) and had no issues until I provisioned a new host (Host2). On Host2, suddenly many of my long-stable Mofs were failing to apply, while Host1 had no issues.

Only by removing the compliant settings on Host1 so that the TestScripts would return $false, was I able to replicate my issues from Host2.

Some notes:

  • Simply migrating to PSDscResources everywhere was not enough to resolve the issue.
  • Explicitly importing (x)PSDesiredStateConfiguration and not importing PSDscResources in a Configuration still resulted in errors.
  • In Script Resources, using Set-StrictMode -Off in the Test and Set bypassed the issue. This option is not available from other Resources such as Registry.

Thankfully, uninstalling the PSDscResources Module returned my Mofs to working as expected on both Host1 and Host2.

After some more research, I found that this behavior is due to Set-StrictMode -Version 'Latest' in every PSDscResources Resource. See below for logs and example Configurations.

Verbose logs showing the problem

Some individual lines and their accompanying code that caused the error:

([IO.Directory]::Exists($installPath)) -or ($AppVersion))
System.Management.Automation.RuntimeException: The variable '$AppVersion' cannot be retrieved because it has not been set.

(($MD5Check.Hash -ne $MD5Current) -or !(Test-Path $MD5Path)) # See below for Configuration and 
System.Management.Automation.RuntimeException: The variable '$MD5Current' cannot be retrieved because it has not been set.

$Needed = $VcList | Where-Object {$_.ProductCode -notin $Installed.ProductCode}
System.Management.Automation.PropertyNotFoundException: The property 'ProductCode' cannot be found on this object. Verify that the property exists.

$Installs = Get-ChildItem "HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" | Get-ItemProperty | Where-Object {$_.Publisher -eq "Publisher, Inc."}
System.Management.Automation.PropertyNotFoundException: The property 'Publisher' cannot be found on this object. Verify that the property exists.

The DSC configuration that is used to reproduce the issue (as detailed as possible)

Both of the examples below failed regardless of which module was imported, as long as PSDscResources was installed. Both executed successfully with expected results before using PSDscResources.

Example1:

$ConfigurationData = @{
    AllNodes = @(
       @{
            NodeName = 'localhost'
        }
    )
}

Configuration BGInfo {
    Import-DscResource -ModuleName 'PSDesiredStateConfiguration'
    Node localhost {
        Script RunBGinfo {
            GetScript = {@{Implemented = 'No'}}
            TestScript = {
                # Bypasses PSDscResources issue for Script Resource
                # Set-StrictMode -Off
                $MD5Path = "C:\Scripts\Resources\BGInfo\BGInfoMD5.txt"
                $ScheduledTask = "C:\Scripts\Resources\BGInfo\BGInfo.xml"
                Try {
                    $Task = Get-ScheduledTask -TaskName "BGInfo" -ErrorAction Stop
                    If (Test-Path $MD5Path) {
                        $MD5Current = Get-Content $MD5Path
                        $MD5Check = Get-FileHash -Path $ScheduledTask -Algorithm MD5
                    }
                    If ($Task -and ($MD5Check.Hash -eq $MD5Current)) {
                        return $true
                    }
                    ElseIf ($null -eq $Task -and ($MD5Check.Hash -eq $MD5Current)) {
                        Remove-Item $MD5Path
                        return $false
                    }
                    Else {
                        return $false
                    }
                }
                Catch {
                    return $false
                }
            }
            SetScript = {
                # Bypasses PSDscResources issue for Script Resource
                # Set-StrictMode -Off
                $ScheduledTask = "C:\Scripts\Resources\BGInfo\BGInfo.xml"
                $MD5Path = "C:\Scripts\Resources\BGInfo\BGInfoMD5.txt"

                Try {
                    $Task = Get-ScheduledTask -TaskName "BGInfo" -ErrorAction Stop
                }
                Catch {
                    Write-Verbose "BGInfo Scheduled Task not found."
                }
                Finally{
                    $MD5Check = Get-FileHash -Path $ScheduledTask -Algorithm MD5
                    If (Test-Path $MD5Path) {
                        $MD5Current = Get-Content $MD5Path
                    }
                    If (($MD5Check.Hash -ne $MD5Current) -or !(Test-Path $MD5Path)) {
                        $MD5Check.Hash | Out-File $MD5Path
                        If ($Task) {
                            $Task | Remove-ScheduledTask
                        }
                        Register-ScheduledTask -Xml (Get-Content $ScheduledTask | Out-String) -TaskName "BGInfo"
                        $Task = Get-ScheduledTask -TaskName "BGInfo" -ErrorAction Stop
                        $Task | Start-ScheduledTask
                    }
                }              
            }
        }
    }
}

BGInfo -OutputPath C:\DSC\BGInfo\ -ConfigurationData $ConfigurationData

Error:

System.Management.Automation.RuntimeException: The variable '$MD5Current' cannot be retrieved because it has not been set.
at System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext funcContext, Exception exception)
at System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)

Example2:

$ConfigurationData = @{
    AllNodes = @(
       @{
            NodeName = 'localhost'
        }
    )
}

Configuration FirefoxPreferences {
    Import-DscResource -ModuleName 'PSDscResources'
    Node localhost {
        #sets default browser to Firefox
        Registry DefaultBrowserHttp {
            Ensure      = "Present"
            Key         = "HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice"
            ValueName   = "ProgId"
            ValueData   = "FirefoxURL"
        }
    }
}

FirefoxPreferences -OutputPath C:\DSC\FirefoxPreferences\ -ConfigurationData $ConfigurationData

Error:

System.Management.Automation.RemoteException: System.InvalidOperationException: The registry key at path HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice already has a value with the name ProgId. To overwrite this registry key value please specifiy the Force parameter as $true.
at System.Management.Automation.Runspaces.AsyncResult.EndInvoke()
at System.Management.Automation.PowerShell.CoreInvokeRemoteHelper[TInput,TOutput](PSDataCollection`1 input, PSDataCollection`1 output, PSInvocationSettings settings)
at System.Management.Automation.PowerShell.CoreInvoke[TInput,TOutput](PSDataCollection`1 input, PSDataCollection`1 output, PSInvocationSettings settings)
at System.Management.Automation.PowerShell.Invoke(IEnumerable input, PSInvocationSettings settings)

Suggested solution to the issue

From what I can tell, no version of Powershell has StrictMode on by default. See documentation here.

This means that for any reasonable person working with Powershell on a regular basis, they would likely not be accustomed to writing scripts to confirm to StrictMode. Instead, many are likely writing code that works in the default PS environment, not necessarily what is perfectly correct.

Additionally, StrictMode was not originally on by default except for a few files (CompositeResourceHelper, GroupResource, UserResource, RunAsHelper). And it is explicitly disabled in PSDesiredStateConfiguration.psm1.

For someone migrating from PSDesiredStateConfiguration to PSDscResources, this behavior of defaulting to Set-StrictMode -Version 'Latest' is a breaking change. This will prevent others like myself from migrating to PSDscResources due to the effort required to revalidate and redeploy a potentially large number of DSC Mofs. If PSDscResources did not override PSDesiredStateConfiguration system-wide, then this might not be such a large issue as the work could be done one Configuration at a time, but that is not the case.

Finally, I would argue that StrictMode being enabled is plainly non-standard for Powershell. It is a nice option, but should not be the default and definitely should not be a forced default.

PSDscResources should disable StrictMode in all of its Resources and optionally create parameters in them to enable this setting.

The operating system the target node is running

OsName               : Microsoft Windows Server 2019 Standard
OsOperatingSystemSKU : StandardServerEdition
OsArchitecture       : 64-bit
WindowsVersion       : 1809
WindowsBuildLabEx    : 17763.1.amd64fre.rs5_release.180914-1434
OsLanguage           : en-US
OsMuiLanguages       : {en-US}

Version and build of PowerShell the target node is running

Name                           Value
----                           -----
PSVersion                      5.1.17763.1007
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.17763.1007
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

Version of the DSC module that was used ('dev' if using current dev branch)

2.12.0.0

@PlagueHO PlagueHO added the discussion The issue is a discussion. label May 3, 2021
@PlagueHO
Copy link
Contributor

PlagueHO commented May 3, 2021

Hi @begna112 - thanks for raising this and apologies for the very long delay in getting back to you. The Set-StrictMode setting was added to the PSDscResource and xPSDesiredStateConfiguration to ensure the PS code within the resource was of high quality and reduce bugs. However, as you point out this does result in any code executed by the Script resource to also adhere to this convention - specifically in your examples that variables are declared before use.

Because the PSDscResource module must remain feature compatible with the in-box PSDesiredStateConfiguration module, it won't likely have any new parameters added to it. This is not the case for the community maintained xPSDesiredStateConfiguration module.

One of the goals moving from PSDesiredStateConfiguration to PSDscResources was to minimize impact, but due to the nature of the move (lots of code was improved and bugs fixed) some DSC config changes was going to be possible. However, it was understood that some configs would require minor changes.

The move away from Set-StrictMode for this module and the xPSDesiredStateConfiguration isn't likely to be done due to the increased difficulty in maintaining a high quality.

However, installing PSDscResources onto a machine should not cause it to be replaced throughout as long as you've specified the resource module to import in your DSC config unless you installed the module to $PSHome (https://docs.microsoft.com/en-us/powershell/scripting/dsc/configurations/import-dscresource?view=powershell-5.1). Can you check you're installing these modules into a location where they won't be automatically imported by PS DSC?

@begna112
Copy link
Author

begna112 commented Jan 6, 2022

Because the PSDscResource module must remain feature compatible with the in-box PSDesiredStateConfiguration module, it won't likely have any new parameters added to it.

If that is the case, why would you change the Strict Mode setting? These changes are specifically breaking compatibility with PSDesiredStateConfiguration in Script resources, and possibly in others, like the Registry resource example above. (It's been so long since I tested that, but I'm fairly certain that it worked as expected without Force, evaluating as True when the value was already correctly set. Whereas with Strict Mode, it gave the error.)

Can you check you're installing these modules into a location where they won't be automatically imported by PS DSC?

Modules/DscResources are installed in C:\Program Files\WindowsPowerShell\Modules\.

@johlju
Copy link
Contributor

johlju commented Jan 23, 2022

This will most likely not change in this module, but for the xPSDesiredStateConfiguration we could move the strict check to the unit tests so strict mode is checked during the test phase, and so it is not used during runtime. Suggest opening an issue in the repo for xPSDesiredStateConfiguration so it can b tracked, and then please send in a PR to xPSDesiredStateConfiguration that make the correct changes to the unit tests and code.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
discussion The issue is a discussion.
Projects
None yet
Development

No branches or pull requests

3 participants