I'm needing to execute a PowerShell script as part of my TFS build pipeline. The PowerShell script is generic and executes a given AWS Cloud Formation template given to it. I need the developer to supply the template with a list of key/value pairs that represent the templates parameters. Since they can use this to execute any Cloud Formation template, the input parameters will vary.
How can I create an input parameter that is key/value based that I can pass as a parameter to another PowerShell object that accepts a Hashmap of parameters?
The following pseudo code is what I'm trying to achieve
param(
[Parameter(Mandatory=$true)][string]$environment,
[KeyValuePair[]]$templateParameters
)
New-CFNStack -StackName $stackName -TemplateURL $fullTemplateUrlPath -Parameters #( $templateParameters)
I can explicitly create the parameters and pass them in like this:
$bucketNameParameter = new-object Amazon.CloudFormation.Model.Parameter
$bucketNameParameter.ParameterKey = "bucketname"
$bucketNameParameter.ParameterValue = "FooBar"
$isVersionedParameter = new-object Amazon.CloudFormation.Model.Parameter
$isVersionedParameter.ParameterKey = "bucketname"
$isVersionedParameter.ParameterValue = "FooBar"
New-CFNStack -StackName $stackName -TemplateURL $fullTemplateUrlPath -Parameters #( $environmentParameter, #isVersionedParameter )
Since each template has completely different parameters they can take, I would like to make this script flexible to facilitate re-use. What is the most PowerShell way of approaching that?
You can accept a [hashtable] instance and create your [Amazon.CloudFormation.Model.Parameter] instances based on its entries:
param(
[Parameter(Mandatory=$true)] [string] $environment,
[hashtable] $templateParameters
)
# Convert the hashtable's entries to an array of
# [Amazon.CloudFormation.Model.Parameter] instances.
$params = $templateParameters.GetEnumerator() | ForEach-Object {
$param = New-Object Amazon.CloudFormation.Model.Parameter
$param.ParameterKey = $_.Key
$param.ParameterValue = $_.Value
$param # output
}
New-CFNStack -StackName $stackName -TemplateURL $fullTemplateUrlPath -Parameters $params
Note the use of .GetEnumerator(), which is necessary in order to enumerate the hashtable's entries and send them through the pipeline; by default, PowerShell sends hashtables as a whole through the pipeline.
Using your (modified-to-be-unique) example values, you'd invoke your script as:
./script.ps1 -environment foo `
-templateParameters #{ bucketName1 = 'FooBar1'; bucketName2 = 'FooBar2' }
Related
G'day everyone,
I'm trying to execute a function in PowerShell with the Parameters coming from a Variable I'm not sure if it's possible in the way I want it to but maybe someone has any idea how I would go about doing that.
$scriptPath = "C:\temp\Create-File.ps1"
$parameters = "-Path C:\temp\testfile.txt -DoSomethingSpecial"
& $scriptPath $parameters
Something along those lines, I don't know in which order the Parameters get entered so I can't use $args[n..m] or binding by position for that. Maybe there is some other Cmdlet I don't know about that is capable of doing that?
Passing an Object as #James C. suggested in his answer allows only to pass parameters in Powershell syntax (e.g. -param1 value1 -param2 value2)
When you need more control over the parameters you pass such as:
double dash syntax for unix style --param1 value1
Slash syntax for Windows style /param1 value1
Equals sign required (or colon) -param1=value1 or -param1:value1
No value for parameter -boolean_param1
additional verbs (values without a param name) value1 value2
you can use an array instead of an object
take ipconfig command for example to renew all connections with "con" in their name:
$cmd = "ipconfig"
$params = #('/renew', '*Con*');
& $cmd $params
or the specific question given example:
$params = #('-Path', 'C:\temp\testfile.txt', '-DoSomethingSpecial')
.\Create-File.ps1 #params
You can use a hastable and Splatting to do this.
Simply set each param name and value in the variable as you would a normal hastable, then pass this in using #params syntax.
The switch param however, needs a $true value for it to function correctly.
$params = #{
Path = 'C:\temp\testfile.txt'
DoSomethingSpecial = $true
}
.\Create-File.ps1 #params
You can run it by Start-Process
Start-Process powershell -ArgumentList "$scriptPath $parameters"
In PowerShell, one can generally call a function with arguments as follows:
DoRoutineStuff -Action 'HouseKeeping' -Owner 'Adamma George' -Multiples 4 -SkipEmail
To trap these 4 supplied arguments at runtime, one might place this inside the function definition
""
"ARGUMENTS:"
$PSBoundParameters
And the resulting object displayed might look like so:
ARGUMENTS:
Key Value
--- -----
Action HouseKeeping
Owner Adamma George
Multiples 4
SkipEmail True
Now, my question is: If I were to manually build the $MyObject identical to $PSBoundParameters displayed above, is there a way to say:
RunFunction 'DoRoutineStuff' -oArgument $MyObject
Again, if it were to be a script file rather than the function DoRoutineStuff, does that make any difference?
Why might one need to do this?
Picture a situation where you need to catch the arguments supplied to first script or function, using $PSBoundParameters, like so:
DoRoutineStuff{
param(
[string]$Action,
[string]$Owner,
[Int]$Multiples,
[switch]$SkipEmail
)
$Data = $PSBoundParameters
#Update one object property
$Data.Multiples = 1
#Then, recursively call `DoRoutineStuff` using `$Data`
#Other tasks
exit;
}
It sounds like the language feature you're looking for is splatting.
You simply pack you're named parameter arguments into a hashtable, store that in a variable and then pass the variable using # in front of its name:
$myArguments = #{
Action = 'HouseKeeping'
Owner = 'Adamma George'
Multiples = 4
SkipEmail = $true
}
Do-Stuff #myArguments
You can also use this technique to only pass a partial set of parameter arguments (or none at all), great for passing along conditional arguments:
$myArguments = #{}
if($someCondition){
$myArguments['Multiples'] = 1
$myArguments['SkipEmail'] = $true
}
if($somethingElse){
$myArguments['Multiple'] = 4
}
Do-Stuff -Action 'HouseKeeping' -Owner 'Adamma George' #myArguments
You can also reuse $PSBoundParameters for splatting further - very useful for proxy functions:
function Measure-Files
{
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $false)]
[string]$Filter,
[Parameter(Mandatory = $false)]
[switch]$Recurse
)
return (Get-ChildItem #PSBoundParameters |Measure-Object -Property Length).Sum
}
I'm calling another runbook using the following code:
$parameters = #{"Table" = 'myTable'}
Start-AutomationRunbook `
-Name "ChildRunBook" `
-Parameters $parameters
the reason I'm using `Start-AutomationRunBook' is that this is in a loop and they need to run in parallel, I don't care about the result.
The Parameters parameter takes in an IDictonary which I have declared first, with one value.
My child runbook has a string parameter called Table defined as:
param(
[parameter(Mandatory=$True)]
[string] $Table
)
and the value that is being passed in is:
{"CliXml":"<Objs Version=\"1.1.0.1\" xmlns=\"http://schemas.microsoft.com/powershell/2004/04\">\r\n <S>myTable</S>\r\n</Objs>"}
How do I either:
parse this string to get the value or;
get the runbook parse the parameter and assign it for me?
Thanks
This behavior is mystifying!
Consider the following PowerShell script:
[Reflection.Assembly]::LoadFrom("Newtonsoft.Json.dll") | Out-Null
function ConvertFrom-JsonNet {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string] $Json
)
$O = [Newtonsoft.Json.Linq.JObject]::Parse($Json)
Write-Host $O.GetType().Name
return $O
}
Clear-Host
$Json = '{"test":"prop"}'
$O1 = ConvertFrom-JsonNet '{"test":"prop"}'
$O2 = [Newtonsoft.Json.Linq.JObject]::Parse($Json)
Write-Host $O1.GetType().Name
Write-Host $O2.GetType().Name
You'd expect the output to be:
JObject
JObject
JObject
but it's not! It's:
JObject
JProperty
JObject
How is this possible? How is the type of the object within the function JObject, but then after it's passed out of the function, it's JProperty?
Sigh
Yay for PowerShell's inflexibility!
Apparently, PowerShell will "unroll" all collections that are destined for the pipeline. In this case, JObject implements ICollection<KeyValuePair<string, JToken>>. The JObject's collection contains a single JProperty, which is what was being "unrolled" into the pipeline. I found this answer, which shows that rolling a collection into an outer collection will cause the intended value to be placed in the pipeline.
Wouldn't it be nice if PowerShell had a mechanism for adding something to the pipeline untouched? :)
In PowerShell I'm using Microsoft.SqlServer.Dac.DacServices and Microsoft.SqlServer.Dac.DacDeployOptions to deploy/update a database DACPAC. The problem I am having is finding where to set the SQLCMD Variables the package requires.
Abbreviated Sample
# Create a DacServices object, which needs a connection string
$dacsvcs = New-Object Microsoft.SqlServer.Dac.DacServices "server=$sqlserver"
# Load dacpac from file
$dp = [Microsoft.SqlServer.Dac.DacPackage]::Load($dacpac)
# Deploy options
$deployOptions = New-Object Microsoft.SqlServer.Dac.DacDeployOptions
$deployOptions.IncludeCompositeObjects = $true
I know I can input these just fine with SqlPackage.exe, and maybe that's what I should do. But no where in the documentation or web grok can I find an example of DacServices usage with SQLCMD variables as an option--SQLCMD variables as required parameters for my project's DACPAC.
You should set options in the $deployOptions.SqlCommandVariableValues property. This is an updateabase Dictionary - you can't assign a new dictionary but you can update the key/value pairs inside it. For example to set a variable "MyDatabaseRef" to "Database123" use
$deployOptions.SqlCommandVariableValues.Add("MyDatabaseRef", "Database123");
The API reference is here.
I have another code snippet to share in relation to this, a method of processing multiple variables from a Powershell script argument;
param(
[hashtable] $SqlCmdVar
)
$deployOptions = New-Object Microsoft.SqlServer.Dac.DacDeployOptions
# Process the Sql Command Variables
#
if ($SqlCmdVar -ne $null)
{
foreach($key in $SqlCmdVar.keys)
{
Write-Verbose -Message "Adding Sql Command Variable ""$key""..."
$deployOptions.SqlCommandVariableValues.Add($key,$SqlCmdVar[$key])
}
}
You would call the script like this;
myscript.ps1 -SqlCmdVar #{ variable1 = "my first value"; variable2 = "my second value"; variableetc = "more values"}