Search code examples
powershell

Powershell ConvertTo-SecureString not recognised if run script inline from cmd


When running from cmd.exe

powershell .\scripts\scriptname.ps1

Which has a line to convert plain text to secure string

[securestring]$secString = ConvertTo-SecureString $SampleString -AsPlainText -Force

I get error:

ConvertTo-SecureString : The 'ConvertTo-SecureString' command was found in the module 'Microsoft.PowerShell.Security', but the module could not be loaded. For more information, run 'Import-Module Microsoft.PowerShell.Security'.

However if I open / run the script direct in powershell it runs fine?

If I try to import the module manually I get:

Import-Module : The following error occurred while loading the extended type data file: Error in TypeData "System.Security.AccessControl.ObjectSecurity": The member AuditToString is already present.
Error in TypeData "System.Security.AccessControl.ObjectSecurity": The member AccessToString is already present.
Error in TypeData "System.Security.AccessControl.ObjectSecurity": The member Sddl is already present.
Error in TypeData "System.Security.AccessControl.ObjectSecurity": The member Access is already present.
Error in TypeData "System.Security.AccessControl.ObjectSecurity": The member Group is already present.
Error in TypeData "System.Security.AccessControl.ObjectSecurity": The member Owner is already present.
Error in TypeData "System.Security.AccessControl.ObjectSecurity": The member Path is already present.

Powershell version is

Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      22621  963

Solution

  • The symptom of your call to powershell.exe, the Windows PowerShell CLI, from cmd.exe (or a batch file) implies that you've launched cmd.exe from PowerShell (Core) 7+.

    In other words: the chain of calls is:

    • PowerShell (Core) (pwsh.exe) -> cmd.exe -> Windows PowerShell (powershell.exe)

    This indirect call from PowerShell (Core) to Windows PowerShell is the problem: It makes the latter use the former's definition of the $env:PSModulePath and therefore results in attempts to load - incompatible - PowerShell (Core) modules.

    Such an indirect call bypasses the logic that is built into PowerShell (Core) when directly invoking powershell.exe (dynamic removal of PowerShell (Core)-specific entries from $env:PSModulePath) and therefore requires a workaround:

    Per GitHub issue #18530, the solution is to (temporarily) reset the PSModulePath environment variable:

    :: From cmd.exe (a batch file).
    set "PSModulePath="
    powershell .\scripts\scriptname.ps1
    

    Note:

    • As js2010 points out, when a process-specific value for PSModulePath is defined, powershell.exe uses this value as-is, which is problematic: unless the value includes the standard locations, required modules may not be discoverable.

    • By contrast, pwsh, the PowerShell (Core) 7+ CLI, is smarter about this: It automatically ensures the presence of all standard locations, even if a process-level value doesn't include them.

    • The exact rules for how the effective $env:PSModulePath value is determined are complex; they are documented as part of the conceptual about_PSModulePath help topic.

    • Note that even the default $env:PSModulePath values place the standard current-user module location first, making it possible to override standard cmdlets.

      • In both editions it is possible to place additional locations first:

        • implicitly, in Windows PowerShell, by defining a process-level value that determines the effective value as-is, as discussed.

        • explicitly, in PowerShell 7+, by including the current-user location in the process-level value, at the end.