Search code examples
functionpowershellvariablesnew-item

New-Item messing up my variable PowerShell


I wrote a very simple script to acquire a random free drive letter. The function finds a random free letter , creates a new empty text file of that drive letter name eg. Q.txt I then return the value as $new_letter but when it comes out of the function somehow newly file created path is a part of the variable C:\AppPack\Logs\Q.txt Q

Is it something New-Item messing up with my $new_letter variable ?


function get_drive_letter()
{
$letter_acquired = $false
Do
{
$new_letter = Get-ChildItem function:[h-z]: -Name | ForEach-Object { if (!(Test-Path $_)){$_} } | random -Count 1 | ForEach-Object {$_ -replace ':$', ''}
    write-host ("RIGHT AFTER " + $new_letter)
    if (!(test-path "C:\AppPack\Logs\$new_letter.txt"))
    {    
    New-Item -Path C:\AppPack\Logs\ -Name "$new_letter.txt" -ItemType "file" 
    write-host ("FROM FUNCTION " + $new_letter)
    $letter_acquired = $true
    return $new_letter
     }
    
    
    else
    {
    write-host ("LETTER USED ALREADY")
    write-host ($new_letter)
    }
}
while($letter_acquired = $false)
}

$drive_letter = $null

$drive_letter = get_drive_letter
write-host ("RIGHT AFTER FUNCTION " + $drive_letter)

OUTPUT :

RIGHT AFTER Q
FROM FUNCTION Q
RIGHT AFTER FUNCTION C:\AppPack\Logs\Q.txt Q

Solution

  • A PowerShell function outputs everything, not just the result of the expression right after return!

    The additional file path you see is the output from New-Item ... - it returns a FileInfo object for the file you just created.

    You can suppress output by assigning it to the special $null variable:

    # Output from New-Item will no longer "bubble up" to the caller
    $null = New-Item -Path C:\AppPack\Logs\ -Name "$new_letter.txt" -ItemType "file"
    return $new_letter
    

    Or by piping to Out-Null:

    New-Item ... |Out-Null
    

    Or by casting the entire pipeline to [void]:

    [void](New-Item ...)
    

    Although I recommend explicitly handling unwanted output at the call site, you can also work around this behavior with a hoisting trick.

    To demonstrate, consider this dummy function - let's say we "inherit" it from a colleague who didn't always write the most intuitive code:

    function Get-RandomSquare {
      "unwanted noise"
    
      $randomValue = 1..100 |Get-Random
    
      "more noise"
    
      $square = $randomValue * $randomValue
    
      return $square
    }
    

    The function above will output 3 objects - the two garbage strings one-by-one, followed by the result that we're actually interested in:

    PS ~> $result = Get-RandomSquare
    PS ~> $result
    unwanted noise
    more noise
    6400
    

    Let's say we've been told to make as few modifications as possible, but we really need to suppress the garbage output.

    To do so, nest the entire function body in a new scriptblock literal, and then invoke the whole block using the dot-source operator (.) - this forces PowerShell to execute it in the function's local scope, meaning any variable assignments persist:

    function Get-RandomSquare {
      # suppress all pipeline output
      $null = . {
        "unwanted noise"
        $randomValue = 1..100 |Get-Random 
        "more noise"
        $square = $randomValue
    
        return $square
      }
    
      # variables assigned in the block are still available
      return $square
    }