PSScriptAnalyzer: false positive 'PSUseDeclaredVarsMoreThanAssignments'

I get the message ‘The variable ‘param’ is assigned but never used.’ My script looks like this:

$param = $MyInvocation.BoundParameters

a couple of lines further down in the same script:

. "$PSScriptRoot\wrap.script.ps1 @param"

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 9
  • Comments: 18 (2 by maintainers)

Most upvoted comments

Just reading through this issue to see if anyone’s already asked for Pester variable special casing (which they have).

I should note here that this rule will never be able to fully correctly detect when a variable isn’t used in every case. There are several ways to go behind its back because of PowerShell’s dynamic nature (dynamic meaning that the only way to know is to execute the code, which is potentially side-effectful – this is known as “undecidable”).

  1. Dynamic scope (the way PowerShell resolves variables, as opposed to lexical scope):

    $x = 7
    
    function Test-X
    {
        $x
    }
    
    function Test-InnerX
    {
        $x = 5
        Test-X
    }
    
    $x             # 7
    Test-X         # 7
    Test-InnerX    # 5
    $x             # 7 (Just to prove that Test-InnerX didn't set the outer $x)
    

    How many times is outer $x referred to here? Depends on where Test-X is called, because the $x it references it resolved at call time, not at definition time like it would be in Python for example.

    Also “call time” here could mean after script execution is started:

    $x = 7
    function Test-X { $x }
    
    [scriptblock]::Create("Test-X").Invoke()
    

    Or even:

    Set-Content -Path ./script.ps1 -Value "Test-X"
    
    function Test-X { $x }
    
    $x = 111
    
    ./script.ps1     # 111
    
  2. Unsual scoping (in some cases impossible to know)

    • $env:var could be used by other processes
    • $global:var could be used by other runspaces
    • $using:var should be straightforward since it’s a local variable copy
    • $script:var is actually easy to check, but different to local
    • $local:var is even easier
  3. Accessing variables through the variable: provider

    $x = 8
    
    Get-Item 'variable:/x' | Write-Output
    
  4. Using Get-/Set-Variable

    $x = 10
    
    Get-Variable -ValueOnly x
    
  5. Using either of the last two with non-constant variable names:

    Set-Content -Path ./variableName.txt -Value "contentOfFile"
    $varname = Get-Content -Raw ./variableName.txt
    "Hi" | Set-Variable $varname
    
    Write-Output $contentOfFile
    
  6. Variables in contexts with mangled scopes (Pester, ForEach-Object, dot-sourcing)

    Describe "Tests" {
      BeforeAll {
        $x = 9
      }
      It {
        $x | Should -Be 9
      }
    }
    

A few of these could be solved in easy cases where constant arguments are given, it’s just a case of “shaving the yak”. But as a static analyzer, PSScriptAnalyzer can only do so much. Ultimately this rule is supposed to be a helpful heuristic rather than an absolute.

I just noticed this bug… It appears any time a variable is assigned inside {} resulting in a false positive being identified by PSScriptAnalyzer.

$bubba = 'Hi there'
1..2 | % { $bubba = 'Some contrived value' }
$bubba

OR

$bubba = ''
{ $bubba = 'assign something here' }
$bubba

bump - This also returns a false positive for global variables even when they are qualified with “$global:”, if the variable is not re-used within the scope of the current block

Ex:

Function MakeTrue {
 $Global:g_GlobalVariable = $true
}

Function MakeFalse {
 $Global:g_GlobalVariable = $false
}

Same here: The variable ‘NewFileArray’ is assigned but never used. Here is the code: `# Create an array to hold the new file attributes (skipping the “Entry #” attribute since that is not stored in the AD attribute field). $NewFileArray = @() $NewFileEntry.PSObject.Properties | Where-Object {$.Name -notlike “Entry”} | ForEach-Object { # Join the name and value of each attribute, using the colon character as a delimiter. $NewFileArray += $.Name + “:” + $_.Value }

Create an ADSI connection to the Service Account defined in the Main Window, and set the custom attribute (extensionAttribute) to the Entry #.

$UpdateAccount = [ADSI]$($SvcAccount.Properties.adspath).ToString() $ADAttribute = “extensionAttribute” + $NewFileEntry.Entry

Check to see if there were any entries in the array after excluding the Entry #.

If ($NewFileArray) { # There were so join them in a contiguous string using the comma character as a delimiter, and then store that string in the custom attribute. $UpdateAccount.$ADAttribute = $NewFileArray -join “,” } Else { # There weren’t so blank the attribute because someone must have cleared them in the Edit Window. $UpdateAccount.$ADAttribute.Clear() }`

This might be covered by some of the other examples but just in case here is another sample where the $ln1 variable is being incorrectly marked as unused: 1, 2, 3 | ForEach-Object -begin { $ln1=0 } -Process { '{0,6}<<:{1}' -f ++$ln1,$_ }

Same here with a Pester test case:

image

Test code in the latest VS Code insiders:

Describe 'incorrect configuration is corrected and registered' {
    BeforeAll {
        ."$here\$sut" @testParams

        $testPrintConfig = Get-PrintConfiguration -PrinterName $testPrinter.PrinterName
    }
    it 'Color' {
        $testPrintConfig.Color | Should -Be $false

    } -Skip:$Skip
    it 'Collate' {
        $testPrintConfig.Collate | Should -Be $false
    } -Skip:$Skip
}