Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Admin vs Non-Admin Mode - Cannot overwrite variable because the variable has been optimized

During some testing today I came across an unexpected issue and I do not understand why it is happening. Below is the code I am using to duplicate the issue. It is only a very small portion of the larger project.

Testing is being cone on Windows 10 Build 1709, if that helps

Both the PS1 File and the BAT File are named the same.

Ways to cause the errors

  • Running the PS1 File via Right-Click - Run with PowerShell will cause the error
  • Opening PowerShell ISE in Non-Admin Mode, then opening/running the script will cause the error
  • Running BAT File as Admin or Non-Admin will cause the error

Ways to avoid the errors

  • Opening PowerShell ISE in Admin Mode, then opening/running the script will not cause the error
  • Adding Script: in front of the variables on the last 2 lines of code will not cause the error no matter how the script is executed
  • Using VSCode, it will work as shown below. Running it in the integrated terminal, it will see it not running as an Admin, it will launch PowerShell.exe outside of VSCode and work without issue

-

Why do I have Script: in front of the variables in the functions? It was the only way I could get variables set in the functions to be used outside the functions. The other 25 or so variables not listed in this post do not have an issue, however, they are not modified like these two are after they are set.

The Questions

  • Why, if running the ISE in Admin Mode, it will work?
  • Why would it not work if it relaunches as an Administrator?
  • Why does VSCode not care and it works regardless?

Something isn't making sense and I cannot pinpoint it.

Here are the errors

Cannot overwrite variable NetFX3 because the variable has been optimized. Try using the New-Variable or Set-Variable cmdlet (without any aliases), or dot-source the command that you are using to set the variable. At C:\Users\a502690530\Desktop\Testing2.ps1:14 char:5 + [string]$Script:NetFX3 = $BAT_Files_Path + "NetFX3.zip" + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : WriteError: (NetFX3:String) [], SessionStateUnauthorizedAccessException + FullyQualifiedErrorId : VariableNotWritableRare

Cannot overwrite variable Power_Plan because the variable has been optimized. Try using the New-Variable or Set-Variable cmdlet (without any aliases), or dot-source the command that you are using to set the variable. At C:\Users\a502690530\Desktop\Testing2.ps1:15 char:5 + [string]$Script:Power_Plan = $BAT_Files_Path + "Power_Plan.zip" + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : WriteError: (Power_Plan:String) [], SessionStateUnauthorizedAccessException + FullyQualifiedErrorId : VariableNotWritableRare

Here is the code

# Checks if running as an administrator. If not, it will relaunch as an administrator
If (-Not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
    $Arguments = "& '" + $MyInvocation.MyCommand.Definition + "'"
    Start-Process Powershell -Verb RunAs -ArgumentList $Arguments
    Break
}

[string]$ErrorActionPreference = "Continue"
[string]$BAT_Files = $Root_Path + "BAT_Files\"

Function Set-FilePaths ([string]$BAT_Files_Path) {
    # BAT Files Paths (ZIPs only!!!)
    [string]$Script:NetFX3     = $BAT_Files_Path + "NetFX3.zip"
    [string]$Script:Power_Plan = $BAT_Files_Path + "Power_Plan.zip"

    Set-Lists
}

function Set-Lists {
    # List of BAT Files (ZIPs)
    [System.Collections.ArrayList]$Script:List_Of_BAT_Files = @(
        $NetFX3
        $Power_Plan
    )
}

Set-FilePaths `
    -BAT_Files_Path $BAT_Files

PAUSE

$NetFX3 = ((Split-Path $NetFX3 -Parent) + "\NetFX3\")
$Power_Plan = ((Split-Path $Power_Plan -Parent) + "\Power_Plan\")

BAT File to launch

REG ADD "HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell" /T REG_SZ /V ExecutionPolicy /D Unrestricted /F

Start PowerShell.exe -Command "& '%~dpn0.ps1'"
like image 847
Clayton Lewis Avatar asked Jun 07 '26 06:06

Clayton Lewis


1 Answers

I have no specific answer, but a pointer:

Your issue sounds like a PowerShell bug related to the DLR (Dynamic Language Runtime), a technology PowerShell uses behind the scenes (since v3); there's at least one open bug report on GitHub that sounds related.


Aside from the workaround you already know of - using scope modifier script consistently - I suggest avoiding variable access across scope boundaries as a general best practice, which should also avoid the problem.

PowerShell is very flexible in what it can return (output) from a function, so it's better to set variables in the caller's scope based on a function's output.

Specifically, I suggest refactoring your code as follows:

Function Get-FilePaths ([string]$BAT_Files_Path) {
  # Output the paths as an *array*.
  ($BAT_Files_Path + "NetFX3.zip"), ($BAT_Files_Path + "Power_Plan.zip")
}

# Call the function in the script scope and capture its output in variables.
$List_Of_BAT_Files = Get-FilePaths

# Use a destructuring assignment to store the elements of the array 
# in individual variables
$NetFX3, $Power_Plan = $List_Of_BAT_Files

If there are a lot of individual variables to set, you can make the function output a hash table instead, and use the hash table's named entries instead of individual variables (requires PSv3+, due to use of [ordered] to create a hash table with ordered keys):

Function Get-FilePaths ([string]$BAT_Files_Path) {
  # Output the paths as a *hash table*, using its
  # entries for named access instead of individual variables.
  $outHash = [ordered] @{
    NetFX3 = $BAT_Files_Path + "NetFX3.zip"
    Power_Plan = $BAT_Files_Path +  "Power_Plan.zip"
  }
  # Add a 'List' entry that contains all values added above as an array.
  # Note the need to use @(...) to force creation of a new array from the
  # hash table's value collection.
  $outHash.List = @($outHash.Values)
  # Output the hash table.
  $outHash
}

# Call the function in the script scope and capture its output in 
# a single variable that receives the hash table.
$hash = Get-FilePaths

# Now you can access the invididual values by name - e.g., $hash.NetFX3 -
# or use $hash.List to get all values.
like image 105
mklement0 Avatar answered Jun 09 '26 00:06

mklement0



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!