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 $_
}
Related
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'
"#
I'm having an issue getting a loop in a function to properly. The goal is to compare the output of some JSON data to existing unified groups in Office 365, and if the group already exists, skip it, otherwise, create a new group. The tricky part is that as part of function that creates the group, it prepends "gr-" to the group name. Because the compare function is comparing the original JSON data without the prepended data to Office 365, the compare function has to have the logic to prepend "gr-" on the fly. If there is a better way to accomplish this last piece, I am certainly open to suggestions.
Here is the latest version of the function. There have been other variations, but none so far have worked. There are no errors, but the code does not identify lists that definitely do exist. I am using simple echo statements for the purpose of testing, the actual code will include the function to create a new group.
# Test variable that cycles through each .json file.
$jsonFiles = Get-ChildItem -Path "c:\tmp\json" -Filter *.json |
Get-Content -Raw
$allobjects = ForEach-Object {
$jsonFiles | ConvertFrom-Json
}
$alreadyCreatedGroup = ForEach-Object {Get-UnifiedGroup | select alias}
# Determine if list already exists in Office 365
function checkForExistingGroup {
[CmdletBinding()]
Param(
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
$InputObject
)
Process {
if ("gr-$($InputObject.alias)" -like $alreadyCreatedGroup) {
echo "Group exists"
} else {
echo "Group does not exist"
}
}
}
$allobjects | checkForExistingGroup
#$alreadyCreatedGroup | checkForExistingGroup
The above code always produces "Group does not exist" for each alias from the JSON data.
The individual variables appear to be outputting correctly:
PS> $alreadyCreatedGroup
Alias
-----
gr-jsonoffice365grouptest1
gr-jsonoffice365grouptest2
gr-jsonoffice365grouptest3
PS> $allobjects.alias
jsonoffice365grouptest3
jsonoffice365grouptest4
If I run the following on its own:
"gr-$($allobjects.alias)"
I get the following output:
gr-jsonoffice365grouptest3 jsonoffice365grouptest4
So on its own it appends the output from the JSON files, but I had hoped by using $InputObject in the function, this would resolve that issue.
Well, a single group will never be -like a list of groups. You want to check if the list of groups contains the alias.
if ($alreadyCreatedGroup -contains "gr-$($InputObject.Alias)") {
echo "Group exists"
} else {
echo "Group does not exist"
}
In PowerShell v3 or newer you could also use the -in operator instead of the -contains operator, which feels more natural to most people:
if ("gr-$($InputObject.Alias)" -in $alreadyCreatedGroup) {
echo "Group exists"
} else {
echo "Group does not exist"
}
And I'd recommend to pass the group list to the function as a parameter rather than using a global variable:
function checkForExistingGroup {
[CmdletBinding()]
Param(
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
$InputObject,
[Parameter(Mandatory=$true, ValueFromPipeline=$false)]
[array]$ExistingGroups
)
...
}
"gr-$($allobjects.Alias)" doesn't produce the result you expect, because the expression basically means: take the Alias properties of all elements in the array/collection $allobjects, concatenate their values with the $OFS character (output field separator), then insert the result after the substring "gr-". That doesn't affect your function, though, because the pipeline already unrolls the input array, so the function sees one input object at a time.
Curious about how to loop through a hash table where each value is an array. Example:
$test = #{
a = "a","1";
b = "b","2";
c = "c","3";
}
Then I would like to do something like:
foreach ($T in $test) {
write-output $T
}
Expected result would be something like:
name value
a a
b b
c c
a 1
b 2
c 3
That's not what currently happens and my use case is to basically pass a hash of parameters to a function in a loop. My approach might be all wrong, but figured I would ask and see if anyone's tried to do this?
Edit**
A bit more clarification. What I'm basically trying to do is pass a lot of array values into a function and loop through those in the hash table prior to passing to a nested function. Example:
First something like:
$parameters = import-csv .\NewComputers.csv
Then something like
$parameters | New-LabVM
Lab VM Code below:
function New-LabVM
{
[CmdletBinding()]
Param (
# Param1 help description
[Parameter(Mandatory=$true,
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[Alias("p1")]
[string[]]$ServerName,
# Param2 help description
[Parameter(Position = 1)]
[int[]]$RAM = 2GB,
# Param3 help description
[Parameter(Position=2)]
[int[]]$ServerHardDriveSize = 40gb,
# Parameter help description
[Parameter(Position=3)]
[int[]]$VMRootPath = "D:\VirtualMachines",
[Parameter(Position=4)]
[int[]]$NetworkSwitch = "VM Switch 1",
[Parameter(Position=4)]
[int[]]$ISO = "D:\ISO\Win2k12.ISO"
)
process
{
New-Item -Path $VMRootPath\$ServerName -ItemType Directory
$Arguments = #{
Name = $ServerName;
MemoryStartupBytes = $RAM;
NewVHDPath = "$VMRootPath\$ServerName\$ServerName.vhdx";
NewVHDSizeBytes = $ServerHardDriveSize
SwitchName = $NetworkSwitch;}
foreach ($Argument in $Arguments){
# Create Virtual Machines
New-VM #Arguments
# Configure Virtual Machines
Set-VMDvdDrive -VMName $ServerName -Path $ISO
Start-VM $ServerName
}
# Create Virtual Machines
New-VM #Arguments
}
}
What you're looking for is parameter splatting.
The most robust way to do that is via hashtables, so you must convert the custom-object instances output by Import-Csv to hashtables:
Import-Csv .\NewComputers.csv | ForEach-Object {
# Convert the custom object at hand to a hashtable.
$htParams = #{}
$_.psobject.properties | ForEach-Object { $htParams[$_.Name] = $_.Value }
# Pass the hashtable via splatting (#) to the target function.
New-LabVM #htParams
}
Note that since parameter binding via splatting is key-based (the hashtable keys are matched against the parameter names), it is fine to use a regular hashtable with its unpredictable key ordering (no need for an ordered hashtable ([ordered] #{ ... }) in this case).
Try this:
for($i=0;$i -lt $test.Count; $i++)
{$test.keys | %{write-host $test.$_[$i]}}
Weirdly, it outputs everything in the wrong order (because $test.keys outputs it backwards).
EDIT: Here's your solution.
Using the [System.Collections.Specialized.OrderedDictionary] type, you guarantee that the output will come out the same order as you entered it.
$test = [ordered] #{
a = "a","1";
b = "b","2";
c = "c","3";
}
After running the same solution code as before, you get exactly the output you wanted.
I'm writing a script with PowerShell and at some point I needed to use ValidateSet on function params. It's a very good feature, but what I need is something more than that.
For example
Function JustAnExample
{
param(
[Parameter(Mandatory=$false)][ValidateSet("IPAddress","Timezone","Cluster")]
[String]$Fields
)
write-host $Fields
}
So this code snippet allows me to choose one item from the list like that
JustAnExample -Fields IPAddress
and then prints it to the screen.
I wonder if there is a possibility to allow to choose multiple values and pass them to function from one Validation set like so
JustAnExample -Fields IPAddress Cluster
Maybe there is a library for that or maybe I just missed something but I really can't find a solution for this.
If you want to pass multiple string arguments to the -Fields parameter, change it to an array type ([String[]]):
param(
[Parameter(Mandatory=$false)]
[ValidateSet("IPAddress","Timezone","Cluster")]
[String[]]$Fields
)
And separate the arguments with , instead of space:
JustAnExample -Fields IPAddress,Cluster
#SokIsKedu, I had the same issue and could not find an answer. So I use the following inside the function:
function Test-Function {
param (
[ValidateSet('ValueA', 'ValueB')]
[string[]] $TestParam
)
$duplicate = $TestParam | Group-Object | Where-Object -Property Count -gt 1
if ($null -ne $duplicate) {
throw "TestParam : The following values are duplicated: $($duplicate.Name -join ', ')"
}
...
}
It does not prevent duplicates being passed in, but it does catch them.
I have the following ScriptBlock defined:
[ScriptBlock]$strSb = {
param(
[Parameter(Mandatory=$false,Position=0)]
[String[]]$Modules = #('String3','String4')
)
Write-Host "Passed in params:"
foreach($m in $Modules){
Write-Host $m
}
$defaultModules = #('String3','String4')
# Add Default Modules back if not present #
foreach($module in $defaultModules){
if($Modules -notcontains $module){
$Modules += $module
}
}
Write-Host "Final set:"
# Load Dependencies #
foreach($m in $Modules){
Write-Host $m
}
}
As the parameter states in the ScriptBlock, I want to be able to pass in an array of strings. When I call $strSb.Invoke(#('String11','String12')) I receive the following:
Passed in params:
String11
Final set:
String11
String3
String4
What I expect is:
Passed in params:
String11
String12
Final set:
String11
String12
String3
String4
Why is the invoke method truncating my array to the first item entered? And how would I go about fixing it so I can pass in an array of strings?
FWIW: I'm working in v2 and v3.
The problem is that the Invoke method takes an array of arguments (kind of like commands that have an -ArgumentList parameter), so each element in your array is parsed as a separate argument. The first argument, 'String11', is assigned to the first postitional parameter, $Modules, and any subsequent arguments are discarded, since there are no more positional parameters. It doesn't matter that $Modules is declared as a string array; since each element of the argument list is a separate argument, you're setting $Modules to an array of one element.
If you use the , operator to indicate that you're passing in a single array argument, it works as intended:
$strSb.Invoke((,#('String11','String12')))
BTW, you don't really need the #, because a comma-separated list of strings is interpreted as an array by default. Not just in this particular context, but in general. So just use this:
$strSb.Invoke((,('String11','String12')))
To prove out the explanation above, try this scriptblock, which is the same except that a second parameter (creatively named $SecondParameter) is declared, and then displayed after the loop that displays the value of the first parameter:
[ScriptBlock]$strSb = {
param(
[Parameter(Mandatory=$false,Position=0)]
[String[]]$Modules = #('String3','String4'),
[String]$SecondParameter
)
Write-Host "Passed in params:"
foreach($m in $Modules){
Write-Host $m
}
Write-Host "`nSecondParameter: $SecondParameter`n"
$defaultModules = #('String3','String4')
# Add Default Modules back if not present #
foreach($module in $defaultModules){
if($Modules -notcontains $module){
$Modules += $module
}
}
Write-Host "Final set:"
# Load Dependencies #
foreach($m in $Modules){
Write-Host $m
}
}
If you then pass in the arguments as you were, $strSb.Invoke(#('String11','String12')), you get these results:
11-26-13 19:02:12.55 D:\Scratch\soscratch» $strSb.Invoke(#('String11','String12'))
Passed in params:
String11
SecondParameter: String12
Final set:
String11
String3
String4
11-26-13 19:02:29.34 D:\Scratch\soscratch»
One last tip, not directly related to the question, is that you can compact the foreach loops by using a pipelines, which is are not only more succinct but generally more efficient. Here's a compacted version of your code:
[ScriptBlock]$strSb = {
param(
[Parameter(Mandatory=$false,Position=0)]
[String[]]$Modules = ('String3','String4')
)
Write-Host "Passed in params:"
$Modules | Write-Host
$defaultModules = 'String3','String4'
# Add Default Modules back if not present #
$defaultModules | ?{$Modules -notcontains $_} | %{$Modules += $_}
Write-Host "Final set:"
# Load Dependencies #
$Modules | Write-Host
}
If I understand what you're doing, you want to take 2 arrays, concatenate them, and ensure uniqueness...
First, Since you have a [Parameter...] on your parameter, you magically get [CmdletBinding()] on the method. This means that you are automatically going to get $Modules split into multiple calls.
Second, ScriptBlock.Invoke() takes a params style array and puts them into the method as separate arguments.
The first thing I would try is to add the attribute to gather all values:
[Parameter(ValueFromRemainingArguments=$true, Position=0, Mandatory=$true)]
[String[]]$Modules
However, for the Join, you can much more easily do something like:
($modules + $defaultModules) | Select -Unique
Not sure exactly why, but it doesn't seem to like that named parameter. Seems to like $args, tho :
[ScriptBlock]$strSb = {
$Modules = $args
Write-Host "Passed in params:"
foreach($m in $modules){
Write-Host $m
}
$defaultModules = #('String3','String4')
# Add Default Modules back if not present #
foreach($module in $defaultModules){
if($Modules -notcontains $module){
$Modules += $module
}
}
Write-Host "Final set:"
# Load Dependencies #
foreach($m in $Modules){
Write-Host $m
}
}
$strSb.Invoke('String11','String12')
Passed in params:
String11
String12
Final set:
String11
String12
String3
String4