I'm finding that in PowerShell 3, ExpandString is truncating my template string and only giving the very beginning of it. This worked in PowerShell 2 without a hitch, so I'm not sure what's going wrong.
The goal is to insert the value of $theSetting into the template string. Note that I use a regex to escape quotes and graves so that PowerShell doesn't try to expand them, and that appears to be working fine.
PS > $theSetting = 'x'
PS > $template = '<?xml version="1.0" encoding="utf-8" ?><AppSettings><Setting value="${theSetting}"/></AppSettings>'
PS > $template = $template -replace "('|`"|``)", '`$1'
PS > $template
<?xml version=`"1.0`" encoding=`"utf-8`" ?><AppSettings><Setting value=`"${theSetting}`"/></AppSettings>
PS > $ExecutionContext.InvokeCommand.ExpandString($template)
<?xml version="
For some reason, it's cutting it off after the first double quote. I appreciate any help in identifying what changed between PowerShell 2 and 3.
As you might guess given the template text, I'm actually loading the template from a file and writing the contents back out, and this is being used for configuration files that are far more variable and complex than the sample template seen here. So something simple like a regex instead isn't really an option.
For the benefit of future readers, don't do this. Use a real templating engine (e.g., Mustache).
Powershell 3 changed the way it parses strings to where you no longer need to change the " to `". I'm not quite sure why it evaluates it as the end of a string though.
Like Keith said, if you remove the replace code you have you will be fine. You can also detect to see if you are running on a lower powershell version and do the replacement like this:
if ($PSVersionTable.PSVersion.Major -lt 3) { $var = $var -replace '"','`"' }
Hope this helps
Related
I am not looking for a working solution but rather an idea to push me in the right direction as I was thrown a curveball I am not sure how to tackle.
Recently I wrote a script that used split command to check the first part of the file against the folder name. This was successful so now there is a new ask: check all the files against the naming matrix, problem is there are like 50+ file formats on the list.
So for example format of a document would be ID-someID-otherID-date.xls
So for example 12345678-xxxx-1234abc.xls as a format for the amount of characters to see if files have the correct amount of characters in each section to spot typos etc.
Is there any other reasonable way of tackling that besides Regex? I was thinking of using multiple splits using the hyphens but don't really have anything to reference that against other than the amount of characters required in each part.
As always, any (even vague) pointers are most welcome ;-)
Although I would use a regex (as also commented by zett42), there is indeed an other way which is using the ConvertFrom-String cmdlet with a template:
$template = #'
{[Int]Some*:12345678}-{[String]Other:xxxx}-{[DateTime]Date:2022-11-18}.xls
{[Int]Some*:87654321}-{[String]Other:yyyy}-{[DateTime]Date:18Nov2022}.xls
'#
'23565679-iRon-7Oct1963.xls' | ConvertFrom-String -TemplateContent $template
Some : 23565679
Other : iRon
Date : 10/7/1963 12:00:00 AM
RunspaceId : 3bf191e9-8077-4577-8372-e77da6d5f38d
I have a Powershell script that is called from the command line.
script.ps1 "\\testfolder" "testinput" "xml" "xml2html.xsl" "testfile" "css"
The script uses these command line arguments:
param([string]$publish_folder, [string]$input_filename, [string]$input_ext, [string]$transformation_filename, [string]$output_filename, [string]$output_ext)
$input_filename and $output_filename may be a full path+filename, the filename only or the filename without extension.
$inputFileNameOnly = [System.IO.Path]::GetFileNameWithoutExtension($input_filename)
$inputPath=$publish_folder+"\"+$inputFileNameOnly+"."+$input_ext
$outputFileNameOnly = [System.IO.Path]::GetFileNameWithoutExtension($output_filename)
$outputPath=$publish_folder+"\"+$outputFileNameOnly+"."+$output_ext
When I run this locally, it works. Output path:
\\testfolder\testfile.css
When I run the same script in an AWS instance, it fails. $inputpath is calculated correctly, but Output path becomes:
\\testfolder\.
so both $output_filename and $output_ext are empty.
The paths are longer than \\testfolder\, but not long enough to cause trouble (about 150 characters). Total length of the arguments doesn't seem to be a problem either.
What could be causing this problem?
$outputPath="$publish_folder+"\"+
Looks like you broke your concatenation here. Double-quote before $publish_folder.
Edit:
Can also be written like this if you don't want to concatenate (+)
$outputPath="$publish_folder\$outputFileNameOnly.$output_ext"
Anything in double-quotes should be expanded correctly.
I set up your script as a function and passed those parameters.
function rename {
param(
[string]$publish_folder,
[string]$input_filename,
[string]$input_ext,
[string]$transformation_filename,
[string]$output_filename,
[string]$output_ext
)
$inputFileNameOnly = [System.IO.Path]::GetFileNameWithoutExtension($input_filename)
$inputPath = $publish_folder+"\"+$inputFileNameOnly+"."+$input_ext
$outputFileNameOnly = [System.IO.Path]::GetFileNameWithoutExtension($output_filename)
$outputPath = $publish_folder+"\"+$outputFileNameOnly+"."+$output_ext
return $outputPath
}
Output is this...
\\\\testfolder\\\\testfile.css
Try passing $publish_folder without ending backslash
Edit: Sorry about formatting. Need some forum practice. =)
With some more testing, we found the cause of the problem.
$outputFileNameOnly = [System.IO.Path]::GetFileNameWithoutExtension($output_filename)
GetFileNameWithoutExtension fails (in the AWS instance) when $output_filename does not contain an extension. On my local machine this worked correctly.
So we added an extension to the command line argument and the script works correctly.
Question about running a custom function in Powershell.
I'm on Windows 10 and I'd like to somehow print my monorepository's directory tree structure excluding node_modules. This is not supported out of the box but requires a custom function to be defined. I found one solution on StackOverflow (https://stackoverflow.com/a/43810460/9654273), which would enable using a command like:
tree -Exclude node_modules -Ascii > tree.txt
The problem is I don't know what to do with the provided source code :D The answer says "add to your $PROFILE, for instance", so I ran notepad $PROFILE in PowerShell, pasted the code snippet there, saved it and tried running the command. It didn't work because I did something wrong. According to the StackOverflow post's comments from anand_v.singh and mklement0 I was still running some other tree command, not the one I just attempted to define.
So how do I use a custom function in PowerShell? Starting point is that source code is on StackOverflow and I don't know where to paste it. Or do you know some other, easier way to print a directory tree on Windows 10 excluding node_modules?
I had the same problem with that function. The issue is the special characters in the hashtable at line 106:
$chars = #{
interior = ('├', '+')[$ndx]
last = ('└', '\')[$ndx] #'
hline = ('─', '-')[$ndx]
vline = ('│', '|')[$ndx]
space = ' '
}
I changed the special characters to ascii as follows:
$chars = #{
interior = ('+', '+')[$ndx]
last = ('\', '\')[$ndx] #'
hline = ('-', '-')[$ndx]
vline = ('|', '|')[$ndx]
space = ' '
}
The only downside is that you do not now have the option of using special graphics characters (the Ascii switch is still there, but does nothing). Maybe someone could tell us how to embed them properly.
I know I can dot source a file:
. .\MyFunctions.ps1
But, I would like to dot source the commands in a string variable:
. $myFuctions
I see that this is possible:
.{$x=2}
And $x equals 2 after the script block is sourced.
But... .{$myFunctions} does not work.
I tried $myFunctions | Invoke-Expression, but it doesn't keep the source function in the current scope. The closest I have been able to come up with is to write the variable to a temporary file, dot source the file, and then remove the file.
Inevitably, someone will ask: "What are you trying to do?" So here is my use case:
I want to obfuscate some functions I intend to call from another script. I don't want to obfuscate the master script, just my additional functions. I have a user base that will need to adjust the master script to their network, directory structure and other local factors, but I don't want certain functions modified. I would also like to protect the source code. So, an alternate question would be: What are some good ways to protect PowerShell script code?
I started with the idea that PowerShell will execute a Base64-encoded string, but only when passed on the command line with -EncodedCommand.
I first wanted to dot source an encoded command, but I couldn't figure that out. I then decided that it would be "obfuscated" enough for my purposes if I converted by Base64 file into a decode string and dot sourced the value of the string variable. However, without writing the decoded source to a file, I cannot figure out how to dot source it.
It would satisfy my needs if I could Import-Module -EncodedCommand .\MyEncodedFile.dat
Actually, there is a way to achieve that and you were almost there.
First, as you already stated, the source or dot operator works either by providing a path (as string) or a script block. See also: . (source or dot operator).
So, when trying to dot-source a string variable, PowerShell thinks it is a path. But, thanks to the possibility of dot-sourcing script blocks, you could do the following:
# Make sure everything is properly escaped.
$MyFunctions = "function Test-DotSourcing { Write-Host `"Worked`" }"
. { Invoke-Expression $MyFunctions }
Test-DotSourcing
And you successfully dot-sourced your functions from a string variable!
Explanation:
With Invoke-Expression the string is evaluated and run in the child scope (script block).
Then with . the evaluated expressions are added to the current scope.
See also:
Invoke-Expression
About scopes
While #dwettstein's answer is a viable approach using Invoke-Expression to handle the fact that the function is stored as a string, there are other approaches that seem to achieve the same result below.
One thing I'm not crystal clear on is the scoping itself, Invoke-Expression doesn't create a new scope so there isn't exactly a need to dot source at that point...
#Define your function as a string
PS> $MyUselessFunction = "function Test-WriteSomething { 'It works!' }"
#Invoke-Expression would let you use the function
PS> Invoke-Expression $MyUselessFunction
PS> Test-WriteSomething
It works!
#Dot sourcing works fine if you use a script block
PS> $ScriptBlock = [ScriptBlock]::Create($MyUselessFunction)
PS> . $ScriptBlock
PS> Test-WriteSomething
It works!
#Or just create the function as a script block initially
PS> $MyUselessFunction = {function Test-WriteSomething { 'It works!' }}
PS> . $MyUselessFunction
PS> Test-WriteSomething
It works!
In other words, there are probably a myriad of ways to get something similar to what you want - some of them documented, and some of them divined from the existing documentation. If your functions are defined as strings, then Invoke-Expression might be needed, or you can convert them into script blocks and dot source them.
At this time it is not possible to dot source a string variable.
I stand corrected! . { Invoke-Expression $MyFunctions } definitely works!
PS > function ]{1}
PS > ]
1
PS >
PS
Why does this work?
What else can I name a function? All I've found so far that works is * and ].
You can name it almost anything. You can even include newlines and emoji* in the name.
function Weird`nFunctionの名前😀 { Write-Host hey }
$c = gcm Weird*
$c.Name
& $c
Escaping helps with lots of things like that:
function `{ { Write-Host cool }
`{
function `0 { Write-Host null }
gci function:\?
I'll add that this is true for variables too, and there's a syntax that removes the need to do most escaping in the variable name: ${varname} (as opposed to $varname).
With that, you could easily do:
${My variable has a first name,
it's
V
A
something
R,
whatever I dunno
🤷} = Get-Process
You'll note that if you then start typing like $MyTAB it will tab complete in a usable way.
To (somewhat) answer why this should work, consider that the variable names themselves are just stored in .Net strings. With that in mind, why should there be a limit on the name?
There will be limits on how some of these names can be used in certain contexts, because the parser will not understand what to do with it if the names don't have certain characters escaped, but literal parsing of PowerShell scripts are not the only way to use functions or variables or other language constructs, as I've shown some examples of.
Being less limiting also means being able to support other languages and cultures by having wide support for character sets.
To this end, here's one more thing that might surprise you: there are many different characters to represent the same or similar things that we take for granted in code, like quotation marks for example.
Some (human) languages or cultures just don't use the same quote characters we do in English, don't even have them on the keyboard. How annoying would it be to type code if you have to keep switching your keyboard layout or use ALT codes to quote strings?
So what I'm getting at here is that PowerShell actually does support many quote characters, for instance, what do you think this might do:
'Hello’
Pretty obvious it's not the "right" set of quotes on the right side. But surprisingly, this works just fine, even though they aren't the same character.
This does have important implications if you're ever generating code from user input and want to avoid sneaky injection attacks.
Imaging you did something like this:
Invoke-Expression "echo '$($userMsg -replace "'","''")'"
Looks like you took care of business, but now imagine if $userMsg contained this:
Hi’; gci c: -recurse|ri -force -whatif;'
For what it's worth, the CodeGeneration class is aware of this stuff ;)
Invoke-Expression "echo '$([System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($userMsg))'"
* PowerShell Console doesn't have good support for Unicode, even though the language does. Use ISE to better see the characters.