Keeping in mind the XY problem, here are my X and Y problems.
X Problem
There are PowerShell scripts, the contents of which I have obfuscated. I am still able to run the script, however.
How this is working is that the scripts are stored inside an executable. PowerShell can execute the executable, asking for a particular script by passing in that script's name as a CLI parameter. The executable's process will then forward the PowerShell script's content through stdout. PowerShell stores the stdout in a string variable, then calls Invoke-Expression, passing in the string, running the obfuscated script.
The problem is that $PSScriptRoot, when it occurs in the obfuscated scripts, is evaluated as $null.
Y Problem
When a PowerShell script, script.ps1 executes a PowerShell script as a string $scriptString, how to get $PSScriptRoot to evaluate as if it were evaluated in script.ps1.
Test 1
# script.ps1
$PSScriptRoot
# Output: Path to directory containing `script.ps1`.
This demonstrates that $PSScriptRoot is non-$null in at least one case.
Test 2
# script.ps1
Invoke-Expression '$PSScriptRoot'
# Output: Nothing
Demonstrates that the way that I am currently executing the string does not work.
Test 3
# script.ps1
. { $PSScriptRoot }
# Output: Path to directory containing `script.ps1`.
Creating a script block using the { ... } syntax does work. This requires the actual commands, however. What I have is a string containing the commands. Putting that string between the curly braces would cause commands to be output rather than run.
Test 4
# script.ps1
. [ScriptBlock]::Create('$PSScriptRoot')
# Output: Nothing
The other way to create a script block is with the C# [ScriptBlock]::Create(...) method. This actually allows creating a script block from a string. Unfortunately, it does not work.
Test 5
# script.ps1
Invoke-Command -ScriptBlock { $PSScriptRoot }
# Output: Path to directory containing `script.ps1`.
Same as test 3, except calling Invoke-Command on the script block rather than dot-sourcing the script block.
Test 6
# script.ps1
$scriptBlock = [ScriptBlock]::Create('$PSScriptRoot')
Invoke-Command -ScriptBlock $scriptBlock
# Output: Nothing.
Same as test 3, except calling Invoke-Command on the script block rather than dot-sourcing the script block.
Current belief about the problem.
- The only way that I know of to execute a string-script as a string
directly is the
Invoke-Expressioncmdlet, which does not work. Maybe there is another way to execute a string directly which might work, but I do not know about. - The other way to execute the string-script is to convert the string into a script block, then execute the script block. There seem to be
two ways of creating a script block, one which works and one which
does not work.
- Creating a script block using the
{ ... }syntax seems to work, but it does not appear to work with strings. - Creating a script block using the
[ScriptBlock]::Create(...)method does not work, but works with strings.
- Creating a script block using the
Questions:
- Is there a way to execute a string directly which will work without calling
Invoke-Expression? - Is there a way to create a script block from a string using the
{ ... }syntax? - What is the reason/mechanism behind the test scenarios working/not working?
- Most importantly, how can we execute a string script and get
$PSScriptRootto evaluate to the proper, non-$nullvalue?
$PSScriptRootis available in the script scope. A script block created with{ ... }has access to the$PSScriptRootscript-scope variable, whereas a script block created with[ScriptBlock]::Create(...). Does not have access to the$PSScriptRootscript-scope variable.{ ... }solves the scope problem except that only commands can be entered in the{ ... }syntax. There does not appear to be a way to get the commands in a string to be the commands of a script block without using the[ScriptBlock]::Create()method.iex 'Split-Path $MyInvocation.InvocationName'