Using Powershell environmental variables as strings when outputting files
Asked Answered
T

3

1

I am using Get-WindowsAutopilotInfo to generate a computer's serial number and a hash code and export that info as a CSV.

Here is the code I usually use:

new-item "c:\Autopilot_Export" -type directory -force

Set-Location "C:\Autopilot_Export"

Get-WindowsAutopilotInfo.ps1 -OutputFile Autopilot_CSV.csv

Robocopy C:\Autopilot_Export \\Zapp\pc\Hash_Exports /copyall

This outputs a CSV file named "Autopilot_CSV.csv" to the C:\Autopilot_Export directory and then Robocopy copies it to the Network Share drive inside of the Hash_Exports folder listed above. In the above code, if I manually type in "test", "123", "ABC", etc. and replace "Autopilot_CSV" it will output a CSV under all of those names as well. So it looks like Get-WindowsAutopilotInfo will create a CSV file and save the file name with whatever string you pass into it. Great.

However, I am running this script on multiple different computers and I would like the exported CSV to be named something unique to the machine it is running on so I can easily identify it once it's copied. I am trying to pass the value of the environmental variable $env:computername as a string input for the CSV and it isn't working.

Here's the code I'm trying to use:

new-item "c:\Autopilot_Export" -type directory -force

Set-Location "C:\Autopilot_Export"

Get-WindowsAutopilotInfo.ps1 -OutputFile $env:computername.csv

Robocopy C:\Autopilot_Export C:\Users\danieln\Desktop /copyall

This code does not generate the CSV file at all. Why not?

Do I just have a basic misunderstanding of how environmental variables are used in Powershell? $env:computername appears to return a string of the computer's name, but I cannot pass it into Get-WindowsAutopilotInfo and have it save, but it will work if I manually type a string in as the input.

I have also tried setting it to a variable $computername = [string]$env:computername and just passing $computername in before the .CSV and that doesn't appear to work either. And per the docmentation, environmental variables are apparently already strings.

Am I missing something?

Thanks!

Thurmanthurmann answered 11/2, 2021 at 17:55 Comment(0)
T
1

Doublequoting seems to work. The colon is special in powershell parameters.

echo hi | set-content "$env:computername.csv"

dir


    Directory: C:\users\me\foo


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----         2/11/2021   1:02 PM              4 COMP001.csv

The colon is special. For example in switch parameters:

get-childitem -force:$true

Actually, it's trying to find a property named csv:

echo hi | Set-Content  $env:COMPUTERNAME.length
dir


    Directory: C:\Users\me\foo


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----         2/11/2021   3:04 PM              4 8
Timmy answered 11/2, 2021 at 18:3 Comment(3)
Thanks so much! Maybe I just don't completely understand environmental variables and how colons work in Powershell. I would have thought since the value of $env:comptername is already a string, it would have worked as such.Thurmanthurmann
Double-quoting is indeed the right solution, but the need for that is unrelated to the use of : (colon). The reason double-quoting is needed here is that $env:computername.csv is otherwise interpreted as trying to access a property named csv on the object stored in variable $env:computername.Redolent
@Redolent True.Timmy
R
2

js2010's answer shows the effective solution: use "..."-quoting, i.e. an expandable string explicitly.

It is a good habit to form to use "..." explicitly around command arguments that are strings containing variable references (e.g. "$HOME/projects") or subexpressions (e.g., "./folder/$(Get-Date -Format yyyy-MM)")

While such compound string arguments generally do not require double-quoting[1] - because they are implicitly treated as if they were "..."-enclosed - in certain cases they do, and when they do is not obvious and therefore hard to remember:

This answer details the surprisingly complex rules, but here are two notable pitfalls if you do not use "...":

  • If a compound argument starts with a subexpression ($(...)), its output is passed as a separate argument; e.g. Write-Output $(Get-Location)/folder passes two arguments to Write-Output: the result of $(Get-Location) and literal /folder

  • If a compound argument starts with a variable reference and is followed by what syntactically looks like either (a) a property access (e.g., $PSVersionTable.PsVersion) or (b) a method call (e.g., $PSHome.Tolower()) it is executed as just that, i.e. as an expression (rather than being considered a variable reference followed by a literal part).

    • Aside #1: Such an argument then isn't necessarily a string, but whatever data type the property value or method-call return value happens to be.

    • Aside #2: A compound string that starts with a quoted string (whether single- or double-quoted) does not work, because the quoted string at the start is also considered an expression, like property access and method calls, so that what comes after is again passed as separate argument(s). Therefore, you can only have a compound strings consisting of a mix of quoted and unquoted substrings if the compound string starts with an unquoted substring or a non-expression variable reference.[2]

The latter is what happened in this case:

  • Unquoted $env:computername.csv was interpreted as an attempt to access a property named csv on the object stored in (environment) variable $env:computername, and since the [string] instance stored there has no csv property, the expression evaluated to $null.

  • By forcing PowerShell to interpret this value as an expandable string via "...", the usual interpolation rules apply, which means that the .csv is indeed treated as a literal (because property access requires use of $(...) in expandable strings).


[1] Quoting is required for strings that contain metacharacters such as spaces.
For values to be treated verbatim, '...' quoting is the better choice (see the bottom section of this answer for an overview of PowerShell string literals).
Also, using neither double- nor single-quoting and individually `-escaping metacharacters is another option (e.g. Write-Output C:\Program` Files.

[2] For instance, Write-Output a"b`t"'`c' and Write-Output $HOME"b`t"'`c' work as intended, whereas Write-Output "b`t"'`c' and Write-Output $HOME.Length"b`t"'`c' do not (the latter two each pass 3 arguments). The workaround is to either use a single "..." string with internal `-escaping as necessary (e.g. Write-Output "${HOME}b`t``c") or to use string-concatenation expression with + enclosed in (...) (e.g. Write-Output ($HOME + "b`t" + '`c'))

Redolent answered 11/2, 2021 at 19:38 Comment(0)
T
1

Doublequoting seems to work. The colon is special in powershell parameters.

echo hi | set-content "$env:computername.csv"

dir


    Directory: C:\users\me\foo


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----         2/11/2021   1:02 PM              4 COMP001.csv

The colon is special. For example in switch parameters:

get-childitem -force:$true

Actually, it's trying to find a property named csv:

echo hi | Set-Content  $env:COMPUTERNAME.length
dir


    Directory: C:\Users\me\foo


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----         2/11/2021   3:04 PM              4 8
Timmy answered 11/2, 2021 at 18:3 Comment(3)
Thanks so much! Maybe I just don't completely understand environmental variables and how colons work in Powershell. I would have thought since the value of $env:comptername is already a string, it would have worked as such.Thurmanthurmann
Double-quoting is indeed the right solution, but the need for that is unrelated to the use of : (colon). The reason double-quoting is needed here is that $env:computername.csv is otherwise interpreted as trying to access a property named csv on the object stored in variable $env:computername.Redolent
@Redolent True.Timmy
H
1

Basically pass it a string rather than the variable:

write-host $env:computername.csv
# output: (no output - looking for a variable called "computername.csv" instead of "computername")

Try the following syntax:

$filename = "$($env:computername).csv"
write-host $filename 

# output: MYPCNAME.csv
Hosfmann answered 11/2, 2021 at 18:10 Comment(2)
"$env:computername.csv" is enough (or "${env:computername}.csv" for conceptual clarity). looking for a variable called "computername.csv" is incorrect - $env:computername.csv, when used in isolation unquoted, tries to access a property named .csv on the value of $env:computernameRedolent
As an aside: I suggest not using Write-Host in sample code, as it might mislead beginners to think that (a) using a cmdlet is necessary to produce output in PowerShell (it isn't) and that (b) the cmdlet to use for producing output is Write-Host (it generally isn't, unless your intent is to write directly to the screen (host) rather than to output data).Redolent

© 2022 - 2024 — McMap. All rights reserved.