PowerShell safe expansion of non printing characters in arbitrary strings - powershell

I have data in an XML file, that will eventually be used as a Registry path, which MAY contain non printing characters (for example when copying the path from a web site into the XML). I want to validate the data and throw a specific error if non printing characters are found.
In Powershell, if I define a variable with non printing characters in single quotes and then test-Path it tests as a valid path as the non printing character is handled as a literal.
Test-Path 'HKEY_LOCAL_MACHINE\SOFTWARE\Test\`n#microsoft.com/GENUINE\#microsoft.com/GENUINE' -isValid
The same thing with double quotes will "expand" the non printing characters and return false, which is what I need.
Test-Path "HKEY_LOCAL_MACHINE\SOFTWARE\Test\`n#microsoft.com/GENUINE\#microsoft.com/GENUINE" -isValid
I have found reference to [string]::Format(() being used to expand the non printing characters, but
$invalidPath = 'HKEY_LOCAL_MACHINE\SOFTWARE\Test\`n#microsoft.com/GENUINE\#microsoft.com/GENUINE'
[string]::Format("{0}",$invalidPath)
does not expand the non printing character as expected.
I have also seen reference to using Invoke-Expression but that is NOT safe, and not an option.
Finally I found $ExecutionContext.InvokeCommand.ExpandString(), which seems to work,
$ExecutionContext.InvokeCommand.ExpandString('HKEY_LOCAL_MACHINE\SOFTWARE\Test\`n#microsoft.com/GENUINE\#microsoft.com/GENUINE')
returns a multiline string to the console, while
$ExecutionContext.InvokeCommand.ExpandString('Write-Host "Screwed"') returns the actual string to the console, rather than actually executing the Write-Host and only returning Screwed to the console.
Finally,
$invalidPath = 'HKEY_LOCAL_MACHINE\SOFTWARE\Test\`n#microsoft.com/GENUINE\#microsoft.com/GENUINE'
Test-Path ($ExecutionContext.InvokeCommand.ExpandString($invalidPath)) -isValid
returns false as expected. Which has me thinking this is the correct approach to pursue, but given all the gotchas elsewhere, I want to be 100% sure there is no way for this approach to be used as a security weak point. Am I on the right track, or are there gotchas my Google-Fu hasn't turned up yet?

Like Invoke-Expression, $ExecutionContext.InvokeCommand.ExpandString() is vulnerable to injection of unwanted commands, except that in the latter case such commands are only recognized if enclosed in $(...), the subexpression operator, as that is the only way to embed commands in expandable strings (which the method's argument is interpreted as).
For instance:
$ExecutionContext.InvokeCommand.ExpandString('a $(Write-Host -Fore Red Injected!) b')
A simple way to prevent this is to categorically treat all embedded $ chars. verbatim, by escaping them with `:
'a $(Write-Host -Fore Red Injected!) b',
'There''s no place like $HOME',
'Too `$(Get-Date) clever by half' |
ForEach-Object {
$ExecutionContext.InvokeCommand.ExpandString(($_ -replace '(`*)\$', '$1$1`$$'))
}
Note: It is sufficient to escape verbatim $ in the input string. A Unicode escape-sequence representation of $ (or ( / )) (`u{24} (or `u{28} / `u{29}), supported in PowerShell (Core) v6+ only), is not a concern, because PowerShell treats the resulting characters verbatim.
Of course, you may choose to report an error if there's a risk of command (or variable-value) injection, which can be as simple as:
$path = 'There''s no place like $HOME'
if ($path -match '\$') { Throw 'Unsupported characters in path.' }
However, this also prevents legitimate use of a verbatim $ in paths.
Taking a step back:
You state that the paths may have been copy-pasted from a web site.
Such a pasted string may indeed contain (perhaps hidden) control characters, but they would be contained verbatim rather than as PowerShell_escape sequences.
As such, it may be sufficient to test for / quietly remove control characters from the string's literal content (before calling Test-Path -IsValid):
# Test for control characters.
if ($path -match '\p{C}') { throw 'Path contains control characters.' }
# Quietly remove them.
$sanitizedPath = $path -replace '\p{C}'

I was initially running PS Version 7.1.3, in which all of these methods bore the same results.
When I ran:
$invalidPath = 'HKEY_LOCAL_MACHINE\SOFTWARE\Test\`n#microsoft.com/GENUINE\#microsoft.com/GENUINE'
Test-Path ($ExecutionContext.InvokeCommand.ExpandString($invalidPath)) -isValid
True is returned
Same as:
Test-Path ([regex]::Escape($invalidPath)) -IsValid
As well as the other methods you mentioned.
In testing in 5.1 I saw the same results as you.
In your XML, would you be seeing something like `n or \n or the actual non-printing characters? IE
'HKEY_LOCAL_MACHINE\SOFTWARE\Test\`n#microsoft.com/GENUINE\#microsoft.com/GENUINE'
OR
'HKEY_LOCAL_MACHINE\SOFTWARE\Test\
#microsoft.com/GENUINE\#microsoft.com/GENUINE'
If the latter is true you should be able to just pass the string to test-path in a variable like Test-Path "$invalidPath" -IsValid to get what you're looking for. In 7.1.3 (maybe earlier) it seems that PS is smart enough to parse those escape sequences and such -- or there is no simple way of doing what you're looking for that I can find.

Related

New-MailboxExportRequest quote issue

I'm trying to get the following command into a program I am developing in PowerShell studio, I'm having some issues figuring out the escaping sequence here, and testing takes forever since I have to import the PST just to view the data :(
Consider the following command which works correctly from the shell...
New-MailboxExportRequest -Mailbox user#domain.com -Name JobName -IncludeFolders "#Inbox#/*","#SentItems#" -ContentFilter {(Received -gt "06/10/2022") -and (Sent -gt "06/10/2022")} -FilePath "\\Server\Folder\MyPST.pst"
I am using variables inside program designer, so my actual code looks like this...
New-MailboxExportRequest -Mailbox $mailbox -Name $jobname -IncludeFolders $IncludeFolders -ContentFilter {(Received -gt "$RecDate") -and (Sent -gt "$SentDate")} -FilePath "$FilePath"
Basically I need to get the first code sample working using variables, however the -IncludeFolders and -ContentFilter parameters require some escaping I can't seem to figure out. Any and all help is much appreciated.
tl;dr:
You need to enclose your -ContentFilter argument in "..." overall in order to support embedding variable values, which you can themselves enclose in embedded '...' quoting:
-ContentFilter "(Received -gt '$RecDate') -and (Sent -gt '$SentDate')"
No special syntax considerations apply with respect to using a variable with -IncludeFolders: Just make sure that variable $IncludeFolders contains an array of strings identifying the target folders; applied to your example:
$IncludeFolders = '#Inbox#/*', '#SentItems#'
If the list of folders must be parsed from user input provided as a single string ($textbox5.text in this example) do the following to convert this single string to an array of names:
# If $textbox5.text contains string '#Inbox#/*, #SentItems#',
# the result is the same as above.
$IncludeFolders = ($textbox5.text -split ',').Trim()
Read on for background information and caveats re -ContentFilter.
Note:
The -ContentFilter information below also applies to other Exchange cmdlets that accept filters, such as Get-Recipient with its -Filter parameter.
It also applies analogously to the -Filter parameter used with AD (Active Directory) cmdlets such as Get-ADUser, although the situation there is complicated by the fact that the AD provider does support evaluation of PowerShell variables, but only in simple cases - see this answer.
New-MailboxExportRequest's -ContentFilter expects a string argument, not a script block ({ ... }), which you've used in your question.
While you can situationally get away with a script block, it is best avoided for two reasons:
When a script block is used as a string (converted to one), string interpolation (embedding the values of variables in the string) is not supported - a script block stringifies to its verbatim content (sans { and }).
Conceptually, use of a script block can give the mistaken impression that an arbitrary piece of PowerShell code may be passed, which is not the case: -ContentFilter supports a domain-specific syntax only that only emulates a limited set of PowerShell features, called OPath filter syntax.
Notably, evaluation of PowerShell variables is not supported - their values must be embedded in the string up front, by PowerShell, using string interpolation.
Therefore:
It's best to use a string argument with -ContentFilter to begin with.
Since you do need to embed variable values, you need string interpolation, via an expandable (double-quoted) string ("..."), inside of which you may quote values with '...' for syntactical convenience.
In case there is no need to embed variable values, use a verbatim (single-quoted) string ('...'), inside of which you may quote values with "...", as in your question. (In effect, this is the equivalent of the (ill-advised) use of { ... }).
Applied to your example: The following embeds the (stringified) values of variables $RecDate and $SentDate in your -ContentFilter argument:
# Note the use of "..." for the outer quoting, and
# '...' for the embedded quoting.
-ContentFilter "(Received -gt '$RecDate') -and (Sent -gt '$SentDate')"
Caveats:
If the values of the variables to embed themselves contain ', use escaped embedded "..." quoting instead; e.g., `"$var`"
If a value could contain ' and/or " and you don't know which, you'd have to use an extra layer of up-front string interpolation on the PowerShell side to escape one of them, depending on the embedded quoting character chosen, using $(...), the subexpression operator, using a -replace operation:
In this escaping you must technically satisfy the OPath filter syntax requirements; the linked help OPath topic suggests that, like in PowerShell, '' can be used to escape ' inside a '...' string; e.g.: -ContentFilter "Body -like '$($var -replace "'", "''")*'"
While, as stated, OPath filters do not support evaluating PowerShell variable references in general, the following automatic variables - which are conceptually constants - are recognized (the linked help topic calls them system values):
$true, $false
$null
Therefore, if you use "..." for the outer quoting (for string interpolation), the $ in these variables must be escaped with ` (the so-called backtick, PowerShell's escape character), to prevent PowerShell from replacing these variable references with their values up front:
`$true, `$false
`$null

How can I create an array of directories and files in PowerShell?

I know that in PowerShell an array can be created as follows.
$myArray = "item1", "item2", "item3"
or using another syntax.
$myfruits = #('Pineapple','Oranges','Bananas')
I need to create an array that has names of directories and a file found in the script root.
$myArray = folder1, folder2, folder3, myprogram.exe
The items in the array are actual names of folders and an executable without quotes as they are found in the current directory (script root). This does not work with PowerShell giving error Missing argument in Parameter list.
What am I doing wrong? I'm on Windows 10 x64 using PowerShell 7.0.
tl;dr
To define an array literal with strings, you must quote its elements:
$myArray = 'folder1', 'folder2', 'folder3', 'myprogram.exe'
Syntax #(...) for array literals should be avoided. Not only is #(...) unnecessary, it is inefficient in PowerShell versions up to 5.0, but - more importantly - it invites conceptual confusion by falsely suggesting that it constructs arrays. For more information, see the bottom section of this answer.
PowerShell has two fundamental parsing modes:
argument mode, which works like traditional shells
In argument mode, the first token is interpreted as a command name (such as cmdlet name, function name, or filename of an executable), followed by a whitespace-separated list of arguments, which may be unquoted, if they contain neither whitespace nor shell metacharacters.
expression mode, which works like traditional programming languages, where strings must be quoted
It is the first token (excluding the LHS of a variable assignment and the =) that determines which mode is applied - see this answer for more information.
In the following statement:
$myArray = folder1, folder2, folder3, myprogram.exe
folder1, because it is unquoted and:
doesn't start with $ or ( or #
isn't a number literal,
triggers argument mode; that is, folder1 is interpreted as the name of a command, which results in the - somewhat unhelpful - error message you saw (you can trigger it with something like Get-Date, too).
Therefore, to define an array literal with strings, you must quote its elements:
$myArray = 'folder1', 'folder2', 'folder3', 'myprogram.exe'
Note that a - convenient, but inefficient - argument-mode alternative that spares you the effort of quoting would be the following:
$myArray = Write-Output folder1, folder2, folder3, myprogram.exe

Powershell get path value from xml file

I'm a beginner of powershell and I'm having trouble reading the path value of an xml file.
Settings.xml
<?xml version="1.0"?>
<Configuration>
<EmailSettings>
<SMTPServer>blabla</SMTPServer>
<SMTPPort>blabla</SMTPPort>
<Path>Repository\Excel</Path>
</EmailSettings>
</Configuration>
To read the data in the XML file I do this way
$ScriptPath = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
[xml]$ConfigFile = Get-Content "$ScriptPath\Settings.xml"
The problem is that if I display the extracted value it is shown correctly, while if I concatenate it with another value, for example to obtain a complete path, I get this
Write-Host $ScriptPath
--> c:\script
write-host $ConfigFile.Configuration.EmailSettings.Path
--> Repository\Excel
write-host $ScriptPath\$ConfigFile.Configuration.EmailSettings.Path
--> c:\script\System.Xml.XmlDocument.Configuration.EmailSettings.Path
How do we do to get the value (in string format??) to be able to concatenate to other variables?
Thank you
You need $(...), the subexpression operator, in order to evaluate an expression such as $ConfigFile.Configuration.EmailSettings.Path inside an expandable string.
By contrast, a simple variable reference such as $ScriptPath is fine without the $(...):
Write-Host "$ScriptPath\$($ConfigFile.Configuration.EmailSettings.Path)"
Note that I've additionally added explicit double-quoting, to make the command more robust - see below for details.
As an aside: Write-Host is generally the wrong tool to use, unless the intent is explicitly to write to the display only, bypassing PowerShell's output streams.
Generally, unquoted tokens that aren't single expressions are implicitly treated like expandable strings, i.e., as if they had been passed in double quotes, and by and large the same rules apply.
However, in the absence of explicit quoting there are additional pitfalls, such as tokens that start with - or the need to escape additional characters, so always double-quoting expandable tokens is a good habit to form.
How PowerShell parses unquoted tokens in argument mode in general is a complex topic - see this answer.

Passing a registry key with an asterisk to Test-Path

I want to run this registry path by Test-Path in PowerShell but it contains an asterisk, which is valid in the registry but not in Windows paths.
The problem is, when I pass it, Test-Path treats the asterisk as a wild card, so this takes a very, very long time because it checks all the sub paths of Classes and is not anything like what I want to test anyway.
Is there any way I can correctly pass that asterisk? Some escape mechanism?
Write-Host "Begin"
Test-Path "HKLM:\SOFTWARE\Classes\*\shell\Some shell extension"
Write-Host "End"
Use the -LiteralPath parameter to prevent globbing/wildcard matching.
-LiteralPath<String[]>
Specifies a path to be tested. Unlike Path, the value of the LiteralPath parameter is used exactly as it is typed. No characters are interpreted as wildcards. If the path includes escape characters, enclose it in single quotation marks. Single quotation marks tell Windows PowerShell not to interpret any characters as escape sequences.
Write-Host "Begin"
Test-Path -LiteralPath "HKLM:\SOFTWARE\Classes\*\shell\Some shell extension"
Write-Host "End"

Powershell -match operator issue

I've written a script that compares two strings, both of which are path names. The first is the full path to a file and the second is a higher level path. I've replaced spaces and backslashes with underscores.
The variables are assigned correctly such as
$full = "__server_department_project_file.txt"
$part = "__server_department_"
The script uses
$testformatch = $full -match $part
In one of my environments this works perfectly (returning TRUE when appropriate). In another completely separate environment this fails constantly (always returns FALSE).
In the failing domain, when I type these things out manually it returns TRUE as expected, but the script always returns FALSE. I've added a testing line that displays the comparisons, copied those results dumped to the screen and manually cut and pasted them into variables in ps command line directly - and those return TRUE.
I'm at a complete loss for what might be causing this. Are there special characters or rules about -match that may be coming into play? Any ideas would be greatly appreciated. Thanks!