Is there a functional difference between -OutVariable and variable assignment? - powershell

I came across the following bizarre behavior and I was hoping someone could point me to some documentation that can explain why this behavior happens or what is causing it.
I have tried looking at the documentation for New-SelfSignedCertificate to see if there was a remark explaining this or a parameter that "forced" it to complete but didn't find anything useful. https://learn.microsoft.com/en-us/powershell/module/pkiclient/new-selfsignedcertificate?view=win10-ps
I also tried searching google for OutVariable vs assignment but didn't find anything helpful either.
Consider the following functions:
function Why-DoesThisFail {
$cert = New-SelfSignedCertificate -Type Custom -Subject "CN=test" -KeyAlgorithm "RSA" -KeyLength 2048 `
-CertStoreLocation "Cert:\CurrentUser\My" -NotAfter (Get-Date).AddDays(1) -KeySpec Signature `
-KeyExportPolicy NonExportable;
# Note that this outputs fine.
Write-Host $cert.Thumbprint;
# Prints nothing
Get-ChildItem -Path "Cert:\CurrentUser\My\$cert.Thumbprint";
}
function Why-DoesThisPass {
New-SelfSignedCertificate -Type Custom -Subject "CN=test" -KeyAlgorithm "RSA" -KeyLength 2048 `
-CertStoreLocation "Cert:\CurrentUser\My" -NotAfter (Get-Date).AddDays(1) -KeySpec Signature `
-KeyExportPolicy NonExportable -OutVariable cert;
# Note that this outputs fine.
Write-Host $cert.Thumbprint;
# Prints Cert as expected
Get-ChildItem -Path "Cert:\CurrentUser\My\$cert.Thumbprint";
}
Notice that the only difference between the 2 functions is one is using variable assignment and one is using the OutVariable. Is this a behavior of Powershell itself in how it is handling OutVariable vs assignment; or is this because of something that New-SelfSignedCertificate is doing behind the covers? It feels almost as if the Certificate isn't being registered on the machine until after the New-SelfSignedCertificate completes and it doesn't complete for some reason when variable assignment is used. Note that after the function completes and control is returned to Powershell, you can successfully run the last line (replacing the thumbprint with the one written from Write-Host) and retrieve the certificate from the machine.
I'm puzzled.
Thanks for any help!

Your issue, is not with any confusion or context with this?
-OutVariable and variable assignment?
It's because you are using the line in the first one wrong. Double quotes do coerce variable expansion, but in your case you need to change the string interpolation for that dotted $cert variable. So, you are having a misunderstanding about string expansion.
This...
Get-ChildItem -Path "Cert:\CurrentUser\My\$cert.Thumbprint";
... should be done this way.
Get-ChildItem -Path "Cert:\CurrentUser\My\$($cert.Thumbprint)"
... otherwise, PowerShell does not know what it is. Also, those semi-colon(s) is never really needed in PowerShell proper. It is a special marker separating distinct code segments. If you had this all on one line, that's at thing, but not how you are using it here.
If you are doing this in the ISE or VSCode, you'll see the color coding change to reflect the which will be expanded and which will not.
See this article.
Powershell String Expansion
Variable Interpolation, or Two Types of Strings Strings with single
quotes (') are literal strings, meaning there are no special
characters; every character in the string will output as itself.
Strings with double quotes (") in powershell will expand variables and
escape characters inside the string; this is referred to as
interpolation.
And this one, though it talks to Here-Strings, it still covers the point I am trying to make here.
Variable expansion in strings and here-strings
Now as for your post question...
Is there a functional difference between -OutVariable and variable
assignment?
... The send result of either is the same. However, this is the deal...
You can store the output of a command in a variable and at the sametime have this output on the screen. This what Out-Variable also does, whereas variable assignment does no output. Yet it can, if you use variable squeezing. So, this...
Get-ChildItem -OutVariable files
... this ...
($files = Get-ChildItem)
... will do the same thing. Assign the results to the variable, and output to the screen at the same time.
See this ---
3 ways to store and display PowerShell Variable simultaneously
Using -OutVariable parameter
PowerShell Variable squeezing Using
Tee-Object Cmdlet
Lastly, avoid the use of Write-Host, unless you are doing text screen coloring. PowerShell, will output to the screen is the default, unless you tell it otherwise. So, this
Write-Host $cert.Thumbprint
Write-Host 'Hello'
... and this …
$cert.Thumbprint
'Hello'
... will do the same thing.
If you must use Write-*, because of your chosen coding practice, then choose Write-Output.
Update for Andrei Odegov point / pointer
Yes, they just output hello to the screen. Try it.
Write-Host 'hello'
# results
hello
'Hello'
# results
Hello
As for your link ...
Is your last statement that Write-Host "Hello" and "Hello" do the same
thing, correct? Look at stackoverflow.com/questions/8755497/…
... these all do the same thing. Output to the screen.
$count = 5
write-host "count=" + $count
# results
count= + 5
write-host "count = $count"
# results
count = 5
"count = $count"
# results
count = 5
"count=" + $count
# Results
count=5
That first one is why string concatenation can be, well, has issues. I am not the only one who thinks so. See this video.
From the authors ofThe Big book of 'The PowerShell Gotcha's'
Don't Concatenate Strings
https://devops-collective-inc.gitbook.io/the-big-book-of-powershell-gotchas
https://www.youtube.com/results?search_query=don%27t+concatenate+string

Related

Again with Powershell: I know how to create a file but I don't know how to write in it [duplicate]

I am creating a script and want to both use Write-Host and Write-Output
As I work I want a backup of information I pull from AD to also become attached to a .txt file.
This is more of a backup in case I miss a piece of information and need to go back and recreate a ticket. Anyways I have a sample of my script, form what I can tell it should be working. If someone with a bit more experience can take a look or point me in the right direction I would appreciate it. If I need to add any more of the script I can provide this. Thanks in Advance.
Import-Module activedirectory
$object = Get-ADUser $sid -Properties * | Select-Object EmailAddress
Write-Host Email: $object.EmailAddress
Write-Output ("Email: $object.EmailAddress") >> C:\psoutput\psoutput.txt -Append
This will create the .txt file of course but is also add other information such as:
Email: #{GivenName=myfirstname; Surname=mylastname; SamAccountName=myid; DisplayName=lastname, firstname - Contingent Worker; City=; EmailAddress=myemailaddress#mywork.com; EmployeeID=; Enabled=True; OfficePhone=; MobilePhone=(555) 555-5555; LockedOut=False; LockOutTime=0; AccountExpirationDate=05/09/2020 00:00:00; PasswordExpired=False; PasswordLastSet=12/03/2019 12:16:37}.EmailAddress
-Append
I am looking to have the output like the following...
name: username
email: user email address
phone: user phone number
etc...
All general information from Active Directory
Thanks again for the suggestions
Don't use write-output. Use (Get-ADUser $sid -properties mail).mail.
Like this:
Add-Content -Path "FilePath" -Value "Email: $((Get-ADUser $sid -properties mail).mail)"
Write-Output ("Email: $object.EmailAddress")
As an aside: No need for (...) here.
This doesn't do what you expect it to: it stringifies $object as a whole and then appends .EmailAddress verbatim; in order to embed an expression, such as accessing a property inside "..." (an expandable string), you need $(), the subexpression operator.
Write-Output "Email: $($object.EmailAddress)" >> C:\psoutput\psoutput.txt
See this answer for an overview of the syntax in PowerShell expandable strings.
Or, more simply, using PowerShell's implicit output behavior (use of Write-Output is rarely necessary):
"Email: $($object.EmailAddress)" >> C:\psoutput\psoutput.txt
>> C:\psoutput\psoutput.txt -Append
>> is effectively an alias for Out-File -Append (just like > is for just Out-File), so not only is there no need for -Append, it isn't interpreted by >>, which accepts only the filename operand.
Instead, -Append was interpreted by Write-Output, which is why it ended up literally in your output file.
Perhaps surprisingly, while a redirection such as >> C:\psoutput\psoutput.txt is typically placed last on the command line, that is not a syntactic requirement: other arguments may follow.
I am looking to have the output like the following..
It sounds like you want formatting as provided by the Format-List cmdlet:
$object | Format-List >> C:\psoutput\psoutput.txt
Note that > / >> / Out-File apply the default string formatting, i.e. the same representation that would by default display in the console.
By using an explicit Format-* cmdlet, you can control that formatting, but note two things about Out-File in general:
As you're outputting for-display formats, the resulting file may not be suitable for further programmatic processing.
To prevent truncation of values, you may have to pass a -Width argument to Out-File, control the enumeration length of nested properties with $FormatEnumerationLimit, and, in the case of Format-Table, specify -AutoSize.
You don't really need to use Write-Output at all. Try this to just get your string to your file:
("Email: " + $object.EmailAddress) >> C:\psoutput\psoutput.txt
You don't need to specify append because '>>' already does that for you

Powershell indexing for creating user logon name

I'm trying to make a user creation script for my company to make things more automated.
I want the script to take the Firstname + Lastname[0] to make the users logon name, but i can't get the syntax right,
I have tried writing {} and () but no luck there.
that's the original peace from my script
New-ADuser...........-UserPrincipalName $fname+$lname[0]
any tips?
Gabriel Luci's helpful answer provides an effective solution and helpful pointers, but it's worth digging deeper:
Your problem is that you're trying to pass expression $fname+$lname[0] as an argument, which requires enclosing (...):
New-ADuser ... -UserPrincipalName ($fname+$lname[0])
PowerShell has two distinct parsing modes, and when a command (such as New-ADUser) is called, PowerShell operates in argument mode, as opposed to expression mode.
Enclosing an argument in (...) forces a new parsing context, which in the case of $fname+$lname[0] causes it to be parsed in expression mode, which performs the desired string concatenation.
In argument mode, unquoted arguments are implicitly treated as if they were enclosed in "...", i.e., as expandable strings under the following circumstances:
If they don't start with (, #, $( or #(.
If they either do not start with a variable reference (e.g., $var) or do start with one, but are followed by other characters that are considered part of the same argument (e.g., $var+$otherVar).
Therefore, $fname+$lname[0] is evaluated as if "$fname+$lname[0]" had been passed:
The + become part of the resulting string.
Additionally, given that inside "..." you can only use variable references by themselves (e.g., $fname), not expressions (e.g., $lname[0]), $lname[0] won't work as intended either, because the [0] part is simply treated as a literal.
Embedding an expression (or a command or even multiple expressions or commands) in "..." requires enclosing it in $(...), the subexpression operator, as in Gabriel's answer.
For an overview of PowerShell's string expansion rules, see this answer.
The following examples use the Write-Output cmdlet to illustrate the different behaviors:
$fname = 'Jane'
$lname = 'Doe', 'Smith'
# WRONG: Same as: "$fname+$lname[0]", which
# * retains the "+"
# * expands array $lname to a space-separated list
# * treats "[0]" as a literal
PS> Write-Output -InputObject $fname+$lname[0]
Jane+Doe Smith[0]
# OK: Use an expression via (...)
PS> Write-Output -InputObject ($fname+$lname[0])
JaneDoe
# OK: Use an (implicit or explicit) expandable string.
PS> Write-Output -InputObject $fname$($lname[0]) # or: "$fname$($lname[0])"
JaneDoe
# OK: Use an intermediate variable:
PS> $userName = $fname + $lname[0]; Write-Output -InputObject $userName
Use a string for the UserPrincipalName, with the variables in the string:
New-ADuser -UserPrincipalName "$fname$($lname[0])"
PowerShell can usually figure out when you put a variable inside a string. When it can't, like in the case of $lname[0], you enclose it in $().
This is called "variable expansion" (other languages, like C#, call it "string interpolation"). Here's a good article that describes it in more detail: https://powershellexplained.com/2017-01-13-powershell-variable-substitution-in-strings/
i just saw the answers and a minute before i realized that i should actually set it up as another variable, $logon = $fname+lname[0]
and pass it as -userPrincipalName $logon.
Thanks for the help, you guy are the best!

How to concatenate string variables with backslashes to create a UNC path in Powershell

This seems like it should be a trivial thing.
I am using Powershell v5.1 and I am trying to build a UNC path from 2 strings that represent a server and a share. Given that $Server hold "Distribution" and $Share holds "Publish"...
$Path = "\\" + $Server + "\" + $Share
The output I am getting for $Path is..
\\Distribution Publish\
I tried changing it to...
$Path = "//" + $Server + "/" + $Share
...as a test thinking the special character "\" was causing a problem, but then I still get the odd sequence where there is a space between server and share and the 2nd slash is at the end.
//Distribution Publish/
What am I missing?
As mentioned in the comments, your symptoms cannot be explained if your $Server and $Share variables truly contain just Distribution and Publish, respectively, with no hidden control characters.
Here's a contrived example of variable values that do produce your symptom:
> $Server = 'Distribution', 'Publish'; $Share = "`r"; "\\" + $Server + "\" + $Share
\\Distribution Publish\
As James C. points out, a more concise alternative for building the string is: "\\$Server\$Share".
As TheIncorrigible1 points out, it is better to build paths using Join-Path.
'Distribution', 'Publish' is an array of strings, whose elements PowerShell concatenates with a space (by default) when converting it to a string, which in this case results in Distribution Publish.
"`r" creates control character CR (0xD), which, when printing to the console, resets the cursor position to the first column and prints the remainder of the string there (which in this case is empty).
Note, however, that the CR is still a part of the string, albeit an invisible one.
If you want an easy way to inspect a given string for hidden control characters, see this answer of mine.
Update:
The OP reports that it turned out to be how the $Server and $Share variables were bound via parameters, through mistaken use of (C#) method-invocation syntax:
# Correct invocation: $Server binds to 1st parameter, $Share to 2nd.
./script $Server $Share
# INCORRECT: Binds *both* values to the *first* parameter, as an *array*
# and assigns nothing to the second.
# In PowerShell, "," constructs an array.
./script($Server, $Share)
The tricky thing is that ./script($Server, $Share) happens to be valid PowerShell syntax too, only with different semantics: $Server, $Share constructs a 2-element array whose elements are the values of $Server and $Share, respectively.
To avoid this pitfall, PowerShell offers strict-mode setting Set-StrictMode -Version 2, which reports an error when method-style invocation is attempted. Note, however, that this setting invariably enables other checks as well, notably causing an error when referencing non-existent properties - see Get-Help Set-StrictMode.
Avoid string addition like you're doing when working with paths; there are cmdlets that handle that.
$Server = '\\This.server.name'
$File = 'something.exe'
$UNC = Join-Path $Server $File
Additionally, do string validation if you're running into weird errors.
If (!$Server) { "Stuff"; Return } # Checks empty string
If (!$File) { "Stuff"; Return }
If (!(Test-Path $UNC)) { "Stuff"; Return } # error checking for the file

powershell confused about array loop, foreach method, string?

I'm trying to export all CA certificates to a directory in Base64 format, I'm new to powershell, since I'm used to doing scripts with bash. Somehow I'm not seeing something that feels like it should be obvious.
This is my line so far,
#(Get-ChildItem -path Cert:\Localmachine\ca).ForEach({Export-Certificate -Type CERT -FilePath "C:\ssl\certs.d\$_.Thumbprint" -Cert "Cert:\LocalMachine\ca\$_.Thumbprint"})
I appreciate any help, as I'm trying to learn how to be idiomatic in PS4.
This line of code contains 3 issues:
First. String interpolation with object property. PS parser doesn't understand "$var.Property", it only understands $expression within "string". But since it's expression, and not just variable name, you can make PS evaluate your line with "$(something to evaluate)". In other words, your -FilePath should be:
-FilePath "C:\ssl\certs.d\$($_.Thumbprint)"
Second. Working with objects. PS underneath is full-blown .Net Framework. Even though many objects are represented in output in a simple, predefined way, actually they are |ed to output as complete live objects. According to MSDN, the -Cert parameter is a <Certificate>, not a string pointing to a certificate, so your -Cert should be simply
-Cert $_
Third. Arrays. Get-ChildItem underneath is nothing more than DirectoryInfo.GetFileSystemInfos() which returns an array of objects. So ideally, you don't need to wrap it with anything and it's possible to simply pipe it further (Get-ChildItem | Foreach-Object{...}). But many people have different tastes on PS syntax, so the form of (gci).ForEach({...}) (without #) has the right to live as well. But what you are doing in form of #(...) is creating a new array of one item being the array returned to you by gci. So technically, it just shouldn't work. It will though, because PS saves you from such mistakes automatically: in PS you can work with array of 1 item in the same way as with this item directly (unless explicitly specified opposite). To illustrate,
#(4).Length # returns 1
#(#(2,3)).Length # returns 2
#(,#(2,3)).Length #returns 1
Thus, your current syntax for Get-ChildItem is error-prone and relies on automatic PS error-handling sugar. I recommend to either remove # in the beginning, or to rewrite in form of
Get-ChildItem -...... | Foreach-Object {...}

Powershell Add-Content -Value with more than one parameter

Okay someone suggested that I start a new question because my original question was solved but I have another question which belongs to my problem from before.
My first problem was that I wanted to write text in a txt file using double quotes. That is solved. My next problem / question is how can I work with more than one parameter in Add-Content -Value?
Here is an example:
Add-Content -Value '"C:\Program Files (x86)\Fraunhofer IIS\easyDCP Creator+\bin\easyDCP Creator+.exe" "-i C:\Users\Administrator\Desktop\dcp_bearbeitet\$title\$title.txt" "-o" "C:\Users\Administrator\Desktop\output_dcp\$title"'
In this case parameter $title stands for the title of the video clip I am working on and I do not know the title when I am working on it, that is why I am using this parameter. But when I am running my script, power-shell totally ignores my parameter. So I tried it again with single quotes around the parameter for example:
... "-i C:\Users\Administrator\Desktop\dcp_bearbeitet\'$title'\'$title'.txt" ...
And then power-shell does not even perform my script. So maybe somebody knows how I can work with parameters in Add-Content -Value?
You can pass arrays into Add-Content.
$string_array = #(
"`n",
"line1",
"line2",
"line3",
'"line4"',
"li`"ne`"5"
)
$string_array | Add-Content file.ext
The parameter -Value can also take an array directly:
Add-Content -path cake.txt -value #('some "stuff"',"more `"backticked`" stuff","hello world")
You have two competing requirements:
you want to substitute a variable into the command you are building so you need to use double quotes
you want to include double quotes in the output, so you either have to escape them or use single quotes round the string.
One way is to use a here-string, i.e. #"..."# to delimit the string instead of just using ordinary quotes:
$value = #"
"C:\Program Files (x86)\Fraunhofer IIS\easyDCP Creator+\bin\easyDCP Creator+.exe" -i "C:\Users\Administrator\Desktop\dcp_bearbeitet\$title\$title.txt" -o "C:\Users\Administrator\Desktop\output_dcp\$title"
"#
Add-Content -Value $value
That should work for you. I separated it out from Add-Content not because it wouldn't work in-place, but because there is less scope for confusion to do one thing at a time. Also if you build the value up separately you can add a temporary Write-Host $value to quickly check you have the string exactly as you want it.
Another way is simply to escape the enclosed quotes:
$value = "`"C:\Program Files (x86)\Fraunhofer IIS\easyDCP Creator+\bin\easyDCP Creator+.exe`" -i `"C:\Users\Administrator\Desktop\dcp_bearbeitet\$title\$title.txt`" -o `"C:\Users\Administrator\Desktop\output_dcp\$title`""
Add-Content -Value $value
but that does get messy.
Other options would be to build up the string in small chunks, or to use a different character instead of the double quotes inside the string and then do a replace to turn them into the quotes, but either of these is messy. I would go for the "here-string" solution.