title: Is it possible to execute script-containing string in script scope?
body: <p>Keeping in mind the <a href="https://en.wikipedia.org/wiki/XY_problem" rel="nofollow noreferrer">XY problem</a>, here are my X and Y problems.</p>
  <h3>X Problem</h3>
  <p>There are PowerShell scripts, the contents of which I have obfuscated. I am still able to run the script, however.</p>
  <p>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 <a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-expression?view=powershell-7.1" rel="nofollow noreferrer">Invoke-Expression</a>, passing in the string, running the obfuscated script.</p>
  <p>The problem is that <a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scripts?view=powershell-7.1#other-script-features" rel="nofollow noreferrer">$PSScriptRoot</a>, when it occurs in the obfuscated scripts, is evaluated as <code>$null</code>.</p>
  <h3>Y Problem</h3>
  <p>When a PowerShell script, <code>script.ps1</code> executes a PowerShell script as a string <code>$scriptString</code>, how to get <code>$PSScriptRoot</code> to evaluate as if it were evaluated in <code>script.ps1</code>.</p>
  <p><strong>Test 1</strong></p>
  <pre><code># script.ps1
  
  $PSScriptRoot
  
  # Output: Path to directory containing `script.ps1`.
  </code></pre>
  <p>This demonstrates that <code>$PSScriptRoot</code> is non-<code>$null</code> in at least one case.</p>
  <p><strong>Test 2</strong></p>
  <pre><code># script.ps1
  
  Invoke-Expression '$PSScriptRoot'
  
  # Output: Nothing
  </code></pre>
  <p>Demonstrates that the way that I am currently executing the string does not work.</p>
  <p><strong>Test 3</strong></p>
  <pre><code># script.ps1
  
  . { $PSScriptRoot }
  
  # Output: Path to directory containing `script.ps1`.
  </code></pre>
  <p>Creating a script block using the <code>{ ... }</code> 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.</p>
  <p><strong>Test 4</strong></p>
  <pre><code># script.ps1
  
  . [ScriptBlock]::Create('$PSScriptRoot')
  
  # Output: Nothing
  </code></pre>
  <p>The other way to create a script block is with the C# <code>[ScriptBlock]::Create(...)</code> method. This actually allows creating a script block from a string. Unfortunately, it does not work.</p>
  <p><strong>Test 5</strong></p>
  <pre><code># script.ps1
  
  Invoke-Command -ScriptBlock { $PSScriptRoot }
  
  # Output: Path to directory containing `script.ps1`.
  </code></pre>
  <p>Same as test 3, except calling <code>Invoke-Command</code> on the script block rather than dot-sourcing the script block.</p>
  <p><strong>Test 6</strong></p>
  <pre><code># script.ps1
  
  $scriptBlock = [ScriptBlock]::Create('$PSScriptRoot')
  Invoke-Command -ScriptBlock $scriptBlock
  
  # Output: Nothing.
  </code></pre>
  <p>Same as test 3, except calling <code>Invoke-Command</code> on the script block rather than dot-sourcing the script block.</p>
  <h3>Current belief about the problem.</h3>
  <ul>
  <li>The only way that I know of to execute a string-script as a string
  directly is the <code>Invoke-Expression</code> cmdlet, which does not work.
  Maybe there is another way to execute a string directly which might
  work, but I do not know about.</li>
  <li>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.
  <ul>
  <li>Creating a script block using the <code>{ ... }</code> syntax seems to work, but it does not appear to work with strings.</li>
  <li>Creating a script block using the <code>[ScriptBlock]::Create(...)</code> method does not work, but works with strings.</li>
  </ul>
  </li>
  </ul>
  <h3>Questions:</h3>
  <ul>
  <li>Is there a way to execute a string directly which will work without calling <code>Invoke-Expression</code>?</li>
  <li>Is there a way to create a script block from a string using the <code>{ ... }</code> syntax?</li>
  <li>What is the reason/mechanism behind the test scenarios working/not working?</li>
  <li>Most importantly, how can we execute a string script and get <code>$PSScriptRoot</code> to evaluate to the proper, non-<code>$null</code> value?</li>
  </ul>