I want to use a string inside a PowerShell script. It should be handed over like a variable when executing the script like this:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -c C:\temp\myscript.ps1 "string"
That is working well with just one word. But my string looks like this and should be handed over:
**<UserInputs><UserInput Question="Gruppenname" Answer="<Values Count="1"><Value DisplayName="Humanresources" Id="af05c5d3-2312-c897-8439-08979d4d0a49" /></Values>" Type="System.SupportingItem.PortalControl.InstancePicker" /><UserInput Question="Ausgabe" Answer="Namen" Type="richtext" /></UserInputs>**
This string contains some quotation marks and I have problems injecting it into my script.
inside the script I have this:
$mystring = $Null if($args[0] -ne $Null) { $mystring = $args[0] } $result = $mystring | Select-String -Pattern "DisplayName="(.*?)"" $result= $result.Matches.Groups[1] $group = $result.value Write-Output "$group" | Out-file C:\temp\group.txt
PowerShell scripts can take parameters, which become variables that are just available to the rest of the script. Use this at the top of your script to setup three variables, one boolean (true false) type, one string type and one integer type.
#stack.ps1
param(
# Param1 help description
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
[string]$MyString="MyDefaultValue",
[int]$MyIntInput,
[bool]$MyTrueFalseInput
)
"Script running!"
"Values recieved!"
"MyString = $MyString"
"MyIntInput = $MyIntInput"
"MyTrueFalse = $MyTrueFalseInput"
Then to pass in values
C:\temp> .\stack.ps1 -MyString Ham -MyIntInput 75 -MyTrueFalseInput $true
Script running!
Values recieved!
MyString = Ham
MyIntInput = 75
MyTrueFalse = True
How to work with odd or complex strings
Now, to pass in a complex string that has quotes, use this syntax:
#using single quotes as a delimiter, ignoring the doubles inside.
$myWeirdString = 'ThisIs"SomeString"WhichHasQuotes"WhichIsWeird'
## extra case, if it's really odd and has both quote types
$myWeirdString = #"
'ThisIs"SomeString"WhichHasQuotes"WhichIsWeird'
"#
Related
Is it possible to take an arg like "this, is, a, test" and create an array in powershell?
Something like
./myScript.ps1 -myStringArray 'this, is, a, test'
And in the code something like:
param (
[string[]] $myStringArray = #()
)
foreach($item in $myStringArray) {
Write-Host $item
}
I have tried a few way but they aren't very clean and either create a sentence with or without the commas but no new line per item.
How difficult would it be to support both:
'this, is, a, test'
And
'this,is,a,test'?
You could use the -split operator with a simple regex to archive that:
param (
[string[]] $myStringArray = #()
)
$myStringArray -split ',\s?' | ForEach-Object {
Write-Host $_
}
Here's a sample powershell script:
$in = read-host -prompt "input"
write-host $in
Here's a sample 'test.txt' file:
hello
And we want to pass piped input to it from powershell. Here's some I have tried:
.\test.ps1 < test.txt
.\test.ps1 < .\test.txt
.\test.ps1 | test.txt
.\test.ps1 | .\test.txt
test.txt | .\test.ps1
.\test.txt | .\test.ps1
get-content .\test.txt | .\test.ps1
even just trying to echo input doesn't work either:
echo hi | \.test.ps1
Every example above that doesn't produce an error always prompts the user instead of accepting the piped input.
Note: My powershell version table says 4.0.-1.-1
Thanks
Edit/Result: To those looking for a solution, you cannot pipe input to a powershell script. You have to update your PS file. See the snippets below.
The issue is that your script \.test.ps1 is not expecting the value.
Try this:
param(
[parameter(ValueFromPipeline)]$string
)
# Edit: added if statement
if($string){
$in = "$string"
}else{
$in = read-host -prompt "input"
}
Write-Host $in
Alternatively, you can use the magic variable $input without a param part (I don't fully understand this so can't really recommend it):
Write-Host $input
You can't pipe input to Read-Host, but there should be no need to do so.
PowerShell doesn't support input redirection (<) yet. In practice this is not a (significant) limitation because a < b can be rewritten as b | a (i.e., send output of b as input to a).
PowerShell can prompt for input for a parameter if the parameter's value is missing and it is set as a mandatory attribute. For example:
function test {
param(
[parameter(Mandatory=$true)] [String] $TheValue
)
"You entered: $TheValue"
}
If you don't provide input for the $TheValue parameter, PowerShell will prompt for it.
In addition, you can specify that a parameter accepts pipeline input. Example:
function test {
param(
[parameter(ValueFromPipeline=$true)] [String] $TheValue
)
process {
foreach ( $item in $TheValue ) {
"Input: $item"
}
}
}
So if you write
"A","B","C" | test
The function will output the following:
Input: A
Input: B
Input: C
All of this is spelled out pretty concisely in the PowerShell documentation.
Yes; in Powershell 5.1 "<" is not implemented (which sucks)
so, this won't work: tenkeyf < c:\users\marcus\work\data.num
but,
this will: type c:\users\marcus\work\data.num | tenkeyf
...
PowerShell doesn’t have a redirection mechanism, but.NET have.
you can use [System.Diagnostics.Process] implements the purpose of redirecting input.
The relevant Microsoft documents are as follows.
Process Class
This is a sample program that works perfectly on my windows 10 computer
function RunAndInput{
$pi = [System.Diagnostics.ProcessStartInfo]::new()
$pi.FileName ="powershell"
$pi.Arguments = """$PSScriptRoot\receiver.ps1"""
$pi.UseShellExecute = $false
$pi.RedirectStandardInput = $true
$pi.RedirectStandardOutput = $true
$p = [System.Diagnostics.Process]::Start($pi)
$p.StandardInput.WriteLine("abc"+ "`r`n");
$p.StandardOutput.ReadToEnd()
$p.Kill()
}
RunAndInput
# OutPut
Please enter the information: abc
Received:abc
# receiver.ps1
('Received:' + (Read-Host -Prompt 'Please enter the information'))
Hope to help you!
I have a script which has quite a lot of lines.
I can easily paste this script in a scriptblock parameter without having to edit it (e.g. put backslashes in front of quotes in the script). I can then encode the script so it can be passed to powershell as en encoded parameter:
$myscript = {
#paste of simplified script
$calc = 6 + 9
echo $calc
}
# Convert script to a string
$command = $carvingScript.ToString()
# Convert string to base64 encoded command
$bytes = [System.Text.Encoding]::Unicode.GetBytes( $command )
$encodedCommand = [Convert]::ToBase64String( $bytes )
I would like to be able to pass one parameter in the script that gets base64 converted. Like this:
$parameter = 9
$myscript = {
$calc = 6 + $parameter
echo $calc
}
Any ideas how to tackle this? I know scriptblock can contain arguments, but in order to parse the argument the whole script needs to be parsed, not just the one parameter
The direct answer to how to add variables to a script block is this:
$parameter = 9
$myscript = #'
$calc = 6 + {0}
echo $calc
'# -f $parameter
$scriptblock = [scriptblock]::Create($myscript)
Basically build it as a string and use the create method from [scriptblock] to convert.
But you can skip creating the scriptblock since you will just convert it back to a string directly afterwards.
It's an old post, but I found this article which worked for me, so I want to share it with you dear community :)
You can use param block with mandatory parameters inside your script block:
$myscript = {
param
(
[Parameter(Mandatory)]
[decimal]
$First,
[Parameter(Mandatory)]
[decimal]
$Second
)
[decimal]($First + $Second)
}
$bytes = [System.Text.Encoding]::Unicode.GetBytes($myscript)
$encodedCommand = [Convert]::ToBase64String($bytes)
$encodedCommand | Set-Content 'C:\temp\encodedCommand.txt' -Encoding UTF8
Then pass parameters by pipeline between two powershell.exe calls:
powershell.exe -noprofile -command "3.3, 2.7" | powershell.exe -encodedcommand DQAKACAAIAAgACAAcABhAHIAYQBtAA0ACgAgACAAIAAgACgADQAKACAAIAAgACAAIAAgACAAIABbAFAAYQByAGEAbQBlAHQAZQByACgATQBhAG4AZABhAHQAbwByAHkAKQBdAA0ACgAgACAAIAAgACAAIAAgACAAWwBkAGUAYwBpAG0AYQBsAF0ADQAKACAAIAAgACAAIAAgACAAIAAkAEYAaQByAHMAdAAsAA0ACgANAAoAIAAgACAAIAAgACAAIAAgAFsAUABhAHIAYQBtAGUAdABlAHIAKABNAGEAbgBkAGEAdABvAHIAeQApAF0ADQAKACAAIAAgACAAIAAgACAAIABbAGQAZQBjAGkAbQBhAGwAXQANAAoAIAAgACAAIAAgACAAIAAgACQAUwBlAGMAbwBuAGQADQAKACAAIAAgACAAKQANAAoAIAAgACAAIABbAGQAZQBjAGkAbQBhAGwAXQAoACQARgBpAHIAcwB0ACAAKwAgACQAUwBlAGMAbwBuAGQAKQANAAoA
This is using Powershell interactive input mode which is visible in the overall output, so be aware if you pass any passwords or secrets:
cmdlet at command pipeline position 1
Supply values for the following parameters:
First: 3.3
Second: 2.7
6.0
If you ever try to have list (array) parameter and pass list of values to the encoded command then you need to remember that last array element must be an empty string - this is how you trick interactive input mode into setting the list parameter.
You also need to remember to do not mark list parameter as ValueFromPipeline otherwise, it will not consume values properly.
$command = {
param
(
[Parameter(Mandatory)]
[string[]]
$MyList
)
$MyList | ForEach-Object { Write-Host $_ }
}
$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encodedCommand = [Convert]::ToBase64String($bytes)
"powershell.exe -noprofile -command `"'test1', 'test2', 'test3', ''`" | powershell.exe -encodedcommand $encodedCommand" | Set-Content 'C:\temp\test.txt' -Encoding UTF8
PS C:\temp> powershell.exe -noprofile -command "'test1', 'test2', 'test3', ''" | powershell.exe -encodedcommand DQAKACAAIAAgACAAcABhAHIAYQBtAA0ACgAgACAAIAAgACgADQAKACAAIAAgACAAIAAgACAAIABbAFAAYQByAGEAbQBlAHQAZQByACgATQBhAG4AZABhAHQAbwByAHkAKQBdAA0ACgAgACAAIAAgACAAIAAgACAAWwBzAHQAcgBpAG4AZwBbAF0AXQANAAoAIAAgACAAIAAgACAAIAAgACQATQB5AEwAaQBzAHQADQAKACAAIAAgACAAKQANAAoAIAAgACAAIAAkAE0AeQBMAGkAcwB0ACAAfAAgAEYAbwByAEUAYQBjAGgALQBPAGIAagBlAGMAdAAgAHsAIABXAHIAaQB0AGUALQBIAG8AcwB0ACAAJABfACAAfQANAAoA
cmdlet at command pipeline position 1
Supply values for the following parameters:
MyList[0]: test1
MyList[1]: test2
MyList[2]: test3
MyList[3]:
test1
test2
test3
I hope it'll help someone in the future. Peace!
Imagine the following code:
# Script Start
$WelcomeMessage = "Hello $UserName, today is $($Date.DayOfWeek)"
..
..
# 100 lines of other functions and what not...
..
function Greet-User
{
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
$WelcomeMessage
}
This is a very basic example, but what it tries to show is a script where there is a $WelcomeMessage that the person running the script can set at the top of the script and controls how/what the message displayed is.
First thing's first: why do something like this? Well, if you're passing your script around to multiple people, they might want different messages. Maybe they don't like $($Date.DayOfWeek) and want to get the full date. Maybe they don't want to show the username, whatever.
Second, why put it at the top of the script? Simplicity. If you have 1000 lines in your script and messages like these spread all over the script, it makes it a nightmare for people to find and change these messages. We already do that for static messages, in the form of localized strings and stuff, so this is nothing new, except for the variable parts in it.
So, now to the issue. If you run that code and invoke Greet-User (assuming the functions/cmdlets for retrieving username and date actually exist and return something proper...) Greet-User will always return Hello , today is.
This is because the string is expanded when you declare it, at the top of the script, when neither $UserName nor $Date objects have a value.
A potential workaround would be to create the strings with single quotes, and use Invoke-Expression to expand them. But because of the spaces, that gets a bit messy. I.e.:
$WelcomeMessage = 'Hello $env:USERNAME'
Invoke-Expression $WelcomeMessage
This throws an error because of the space, to get it to work properly it would have to be declared as such:
$WelcomeMessage = 'Hello $env:USERNAME'
$InvokeExpression = "`"$WelcomeMessage`""
Messy...
Also, there's another problem in the form of code injection. Since we're allowing the user to write their own welcome message with no bounds specified, what's to prevent them from putting in something like...
$WelcomeMessage 'Hello $([void] (Remove-Item C:\Windows -Force -Recurse))'
(Yes, I know this will not delete everything but it is an example)
Granted this is a script and if they can modify that string they can also modify everything else on the script, but whereas the example I gave was someone maliciously taking advantage of the nature of the script, it can also happen that someone accidentally puts something in the string that ends up having unwanted consequences.
So... there's got to be a better way without the use of Invoke-Expression, I just can't quite thing of one so help would be appreciated :)
Embedding variables into strings is not the only way to create dynamic text, the way I would do it is like this:
$WelcomeMessage = 'Hello {0}, today is {1}'
# 100 lines of other functions and what not...
function Greet-User
{
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
$WelcomeMessage -f $Username, $Date
}
The canonical way to delay evaluation of expressions/variables in strings is to define them as single-quoted strings and use $ExecutionContext.InvokeCommand.ExpandString() later on.
Demonstration:
PS C:\> $s = '$env:COMPUTERNAME'
PS C:\> $s
$env:COMPUTERNAME
PS C:\> $ExecutionContext.InvokeCommand.ExpandString($s)
FOO
Applied to your sample code:
$WelcomeMessage = 'Hello $UserName, today is $($Date.DayOfWeek)'
...
...
...
function Greet-User {
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
$ExecutionContext.InvokeCommand.ExpandString($WelcomeMessage)
}
Have you considered using a lambda expression; i.e. instead of defining the variable as a string value define it as a function, then invoke that function passing the relevant parameters at runtime.
$WelcomeMessage = {param($UserName,$Date);"Hello $UserName, today is $($Date.DayOfWeek) $([void](remove-item c:\test\test.txt))"}
#...
# 100 lines of other functions and what not...
#...
"testfile" >> c:\test\test.txt #ensure we have a test file to be deleted
function Get-UserNameFromSomewhereFancy(){return "myUsername";}
function Get-DateFromSomewhereFancy(){return (get-date);}
function Greet-User
{
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
$WelcomeMessage.invoke($username,$date)
}
cls
Greet-User
Update
If you only wish to allow variable replacement the below code would do the trick; but this fails to do more advanced functions (e.g. .DayOfWeek)
$WelcomeMessage = 'Hello $Username, today is $($Date.DayOfWeek) $([void](remove-item c:\test\test.txt))'
#...
# 100 lines of other functions and what not...
#...
"testfile" >> c:\test\test.txt #ensure we have a test file to be deleted
function Get-UserNameFromSomewhereFancy(){return "myUsername";}
function Get-DateFromSomewhereFancy(){return (get-date);}
function Resolve-WelcomeMessage(){
write-output {param($UserName,$Date);"$WelcomeMessage";}
}
function Greet-User
{
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
$temp = $WelcomeMessage
get-variable | ?{#('$','?','^') -notcontains $_.Name} | sort name -Descending | %{
$temp = $temp -replace ("\`${0}" -f $_.name),$_.value
}
$temp
}
cls
Greet-User
Update
To avoid code injection this makes use of -whatif; that will only help where the injected code supports the whatif functionality, but hopefully better than nothing...
Also the code now doesn't require parameters to be declared; but just takes those variables which are available at the time of execution.
$WelcomeMessage = {"Hello $Username, today is $($Date.DayOfWeek) $([void](remove-item c:\test\test.txt))"}
#...
# 100 lines of other functions and what not...
#...
function Get-UserNameFromSomewhereFancy(){return "myUsername";}
function Get-DateFromSomewhereFancy(){return (get-date);}
function Resolve-WelcomeMessage(){
write-output {param($UserName,$Date);"$WelcomeMessage";}
}
"testfile" >> c:\test\test.txt #ensure we have a test file to be deleted
function Greet-User {
[cmdletbinding(SupportsShouldProcess=$True)]
param()
begin {$original = $WhatIfPreference; $WhatIfPreference = $true;}
process {
$Username = Get-UserNameFromSomewhereFancy
$Date = Get-DateFromSomewhereFancy
& $WelcomeMessage
}
end {$WhatIfPreference = $original;}
}
cls
Greet-User
How can I force string variable expansion?
I need to read a string with one or more variable names in it (a template) and then expand it after I read the file. The key is that I must read the contents of the file before I declare the variables that will be used in the expansion. I've tried several ways but I can't get it to work.
It's not an option to read the file after $environment is defined.
Contents of name.txt:
$environment-RPT-INT
#example 1
$name = gc "c:\temp\name.txt"
$environment = "9065DEV"
$expanded = $ExecutionContext.InvokeCommand.ExpandString($name)
$expanded
#example 2
$name = gc "c:\temp\name.txt"
$environment = "9065DEV"
$expanded = $expanded = Invoke-Expression "`"$template`""
$expanded
#example 3
$name = gc "c:\temp\name.txt"
$environment = "9065DEV"
$name = $name.Clone()
$expanded = $ExecutionContext.InvokeCommand.ExpandString($name)
$expanded
Any help is appreciated.
Updated: Example 1 is now working for me.
It looks like you've found some possible solutions, but I'll suggest another that is in my opinion a bit smarter and more robust.
Instead of requiring variable names in your text file, why not use format specifiers. For example, the contents of name.txt:
{0}-RPT-INT
And in your script:
$name = gc "c:\temp\name.txt"
$environment = "9065DEV"
$expanded = $name -f $environment
$expanded
This way, you can rename the variable w/o changing any of your text files. As a bonus, if your text file comes from unknown sources, your script is vulnerable to code injection. For example, say you are given a text file with these contents:
$(rm -whatif -recurse -force c:\)-RPT-INT
Then ExpandString or Invoke-Expression will happily execute that code.
Your Invoke-Expression example is pretty close. Instead of using $template though, you need to use $name.
#example 2
$name = gc 'c:\temp\name.txt';
$environment = '9065DEV';
$expanded = Invoke-Expression -Command "`"$name`"";
$expanded;
If you are willing to store your Setting/Values in a CSV, I wrote a module to pull values from a CSV, put it into a HereString... any variables you put into the CSV become fully expanded inside the Here-String. That way, you can normally address the field names and values.
I might be able to change this to also work with traditional INI's if anyone is interested.
https://github.com/Inventologist?tab=repositories
Look for: HereStringFromCSV
There is a function called ExpandString predefined in powershell. It's inside $ExecutionContext, as follows.
$mystring = #'
This is a here string with some embedded variables.
Here is variable foo -- $foo
Here is variable bar -- $bar
Here is variable bletch -- $bletch
'#
#This displays the here string as is.
$mystring
#now define foo, bar and bletch
$foo = 5
$bar = Get-Date
$bletch = "George Washington"
#now run the here string through Expandstring.
$ExecutionContext.InvokeCommand.ExpandString($mystring)