Monad in PowerShell? - powershell

I have the following code
function Get($url) {
$x = Invoke-RestMethod $url
# ....
$result # return a json which some values (id, name, color) and a list
}
Get "http://...." |
% {
$id = $_.id
$url = "${$_.url}/xxx"
Get $ur |
% {
$name = $_.name
$url = $_.url
Get $url |
% {
$color = $_.color
$url = $_.url
Get $url | % {
# other layer omitted
}
}
}
}
The code looks bad. It seems the embedded layers should be resolved by monad. Is there a way to do it in PowerShell?
The following shows a pseudo F# code.
myMonad {
let! (id1, _, _, urls1, _ ) = Get ["http://..."]
let! (_, name2, _, urls2, _ ) = Get urls1
let! (_, _, color3, urls3, names) = Get urls2
// ....
printfn "%d %s %s %A" id1, name2, color3, names
}

PowerShell is clearly not a function language, though it has some functional traits and its many constructs and dynamic nature allow you to emulate functional features:
The code below defines a recurse-and-aggregate function named recurse that can invoke your Get function as follows in order to aggregate properties Id, Name, and Color from recursive calls to Get via each returned object's .URL property:
recurse -Function Get <# target function #> `
-Argument http://example.org <# input URL #> `
-RecurseOn URL <# what result property to recurse on #> `
-PropertyNames Id, Name, Color <# what properties to aggregate successively,
in each iteration #>
recurse source code with sample code:
# A fairly generic recurse-and-aggregate function that aggregates properties from
# result objects from recursive calls to a given function based on a single result property.
function recurse($Function, $Arguments, $RecurseOn, $PropertyNames, $outProperties = [ordered] #{ }) {
# Call the target function with the specified arguments.
$result = & $Function $Arguments
# Split into the names of the current and the remaining properties to aggregate.
$propName, $remainingPropNames = $PropertyNames
# Add the value of the current property of interest to the output hashtable, if present.
if ($null -ne $result.$propName) {
if ($outProperties.Contains($propName)) { # not the first value
if ($outProperties.$propName -is [array]) { # already an array -> "extend" the array.
$outProperties.$propName += $result.$propName
}
else { # not an array yet -> convert to array, with the previous value and the new one as the elements.
$outProperties.$propName = $outProperties.$propName, $result.$propName
}
}
else { # first value -> assign as-is
$outProperties.$propName = $result.$propName
}
}
if ($remainingPropNames) {
# Recurse on the value(s) of the property specfied with -RecurseOn
foreach ($newArgument in $result.$RecurseOn) {
$null = recurse -Function $function -Argument $newArgument -RecurseOn $RecurseOn -PropertyNames $remainingPropNames -outProperties $outProperties
}
}
# Return the aggregated properties.
$outProperties
}
# Sample input function:
# It makes a REST call to the given URL and returns the
# result object.
function Get($url) {
Write-Host "Get $url"
$id = 1
$name = ''
$color = ''
if ($url -eq 'http://example.org') {
$urls = 'http://example.org/1', 'http://example.org/2'
$id = 1
}
elseif ($url -match 'http://example.org/\d$') {
$urls = "$url/a", "$url/b"
$name = 'test'
}
elseif ($url -match 'http://example.org/\d/a') {
$urls = '...'
$name = 'test'
$color = "[color of $url] #1", "[color of $url] #2"
}
elseif ($url -match 'http://example.org/\d/b') {
$urls = '...'
$name = 'test'
$color = "[color of $url] #1", "[color of $url] #2"
}
[pscustomobject] #{
URL = $urls
Id = $id
Name = $name
Color = $color
}
}
# Call the recurse-and-aggregate function, passing it the name of the Get()
# function, an input URL, what result property to recurse on, and a list of properties to aggregate.
# The output is an ordered hashtable containing the aggregated property values.
recurse -Function Get <# target function #> `
-Argument http://example.org <# input URL #> `
-RecurseOn URL <# what result property to recurse on #> `
-PropertyNames Id, Name, Color <# what properties to aggregate successively,
in each iteration #>
The above yields:
Name Value
---- -----
Id 1
Name {test, test}
Color {[color of http://example.org/1/a] #1, [color of http://example.org/1/a] #2, [color of http://example.org/1/b] #1, [color of htt…

Well, you could insert F# code into Powershell with Add-Type. See Example 7: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/add-type?view=powershell-5.1

Related

How to convert a collection of hashtables to a table within a file?

We're trying to write an array of ordered hashtables to a file. While doing this we would like to to be aligned properly in a table format. A bit similar to Format-Table but differently presented.
The code below works fine for the first case but not for the second one. In the second one we have a property that is of type array and is incorrectly assessed. It feels a bit like we're reinventing the wheel here, is there a better solution?
Usually the values will be of type string, string[] or boolean, but what if another type is passed? Thanks for your help.
Desired goals
Describe 'ConvertTo-TextTableHC' {
It 'should convert a collection of hashtable to a text table' {
$testParameters = #(
[ordered]#{
Name = 'ScriptName'
Value = 'Get printers'
Type = 'String'
Mandatory = $true
}
[ordered]#{
Name = 'PrinterName'
Value = ''
Type = 'String[]'
Mandatory = $false
}
)
$actual = ConvertTo-TextTableHC -Name $testParameters
$expected = #"
Name Value Type Mandatory
ScriptName Get printers String True
PrinterName String[] False
"#
$actual | Should -Be $expected
}
It 'should convert a collection of hashtable to a text table' {
$testParameters = #(
[ordered]#{
Mandatory = $true
Name = 'ScriptName'
Value = 'Get printers'
Type = 'String'
}
[ordered]#{
Mandatory = $false
Name = 'OU'
Value = #('ou1', 'ou2')
Type = 'String[]'
}
)
$actual = ConvertTo-TextTableHC -Name $testParameters
$expected = #"
Mandatory Name Value Type
True ScriptName Get printers String
False OU ou1, ou2 String[]
"#
$actual | Should -Be $expected
} -Tag test
}
This is the function:
Function ConvertTo-TextTableHC {
[CmdletBinding()]
Param (
[Parameter(Mandatory)]
[System.Collections.Specialized.OrderedDictionary[]]$Name,
[int]$Spacing = 1
)
$result = [String[]]::new($Name.Count + 1)
foreach ($prop in $($name[0].Keys)) {
$rows = , $prop + $Name.$prop
$maxChars = ($rows | Measure-Object -Maximum -Property Length).Maximum + $Spacing
for ($i = 0; $i -lt $rows.Count; $i++) {
$spaces = ' ' * (
$maxChars -
($rows[$i] | Measure-Object -Character).Characters
# fails on boolean values:
# ($rows[$i] | Measure-Object -Maximum -Property Length).Maximum
)
$result[$i] += '{0}{1}' -f $rows[$i], $spaces
}
}
# remove spaces from last column
$result = $result.ForEach({$_.Trim()})
$result -join "`r`n"
}

Grab parameters from url and drop them to powershell variables

i have a powershell listener running on my windows-box. Code from Powershell-Gallery: https://www.powershellgallery.com/packages/HttpListener/1.0.2
The "Client" calls the listener with the following example url:
With Powershell i do:
invoke-webrequest -Uri "http://127.0.0.1:8888/Test?id='1234'&param2='##$$'&param3='This is a test!'"
I have no idea, how to drop the parameters from the url to variables with the same name in powershell. I need to bring the parameters to powershellvariables to simply echo them. this is my last missing part. The parameters are separated with & and parameternames are case-sensitive.
To be more detailed, the id from the url should be in a powershell-variable named $id with the value 1234. The Variables can contain spaces, special characters, numbers, alphas. They are case sensitive. The parametervalue could be "My Name is "Anna"! My Pets Name is 'Bello'. " with all the "dirty" Characters like '%$"!{[().
Can someone point me to the right way how to get this solved?
In a valid url, the $ characters should have been escaped to %24
The other 'dirty' character % in Url escaped form is %25
This means the example url is invalid and should be:
$url = "http://127.0.0.1:8888/Test?id='1234'&param2='##%24%24'&param3='This is a test!'"
Then the following does work
$url = "http://127.0.0.1:8888/Test?id='1234'&param2='##%24%24'&param3='This is a test!'"
if ($url -is [uri]) {
$url = $url.ToString()
}
# test if the url has a query string
if ($url.IndexOf('?') -ge 0) {
# get the part of the url after the question mark to get the query string
$query = ($url -split '\?')[1]
# or use: $query = $url.Substring($url.IndexOf('?') + 1)
# remove possible fragment part of the query string
$query = $query.Split('#')[0]
# detect variable names and their values in the query string
foreach ($q in ($query -split '&')) {
$kv = $($q + '=') -split '='
$varName = [uri]::UnescapeDataString($kv[0]).Trim()
$varValue = [uri]::UnescapeDataString($kv[1])
New-Variable -Name $varname -Value $varValue -Force
}
}
else {
Write-Warning "No query string found as part of the given URL"
}
Prove it by writing the newly created variables to the console
Write-Host "`$id = $id"
Write-Host "`$param2 = $param2"
Write-Host "`$param3 = $param3"
which in this example would print
$id = '1234'
$param2 = '##$$'
$param3 = 'This is a test!'
However, I personally would not like to create variables like this, because of the risk of overwriting already existing ones.
I think it would be better to store them in a hash like this:
# detect variable names and their values in the query string
# and store them in a Hashtable
$queryHash = #{}
foreach ($q in ($query -split '&')) {
$kv = $($q + '=') -split '='
$name = [uri]::UnescapeDataString($kv[0]).Trim()
$queryHash[$name] = [uri]::UnescapeDataString($kv[1])
}
$queryHash
which outputs
Name Value
---- -----
id '1234'
param2 '##$$'
param3 'This is a test!'

Return all SSRS reports in a folder with the data source name and ConnectString

This what I have so far. However, I want to list every report to it's connection string. I don't see a unique identifier in the GetDataSourceContents() method to join the report and data source lists.
$ReportServerUri = "YOUR_SERVER";
$rs = New-WebServiceProxy -Uri $ReportServerUri -UseDefaultCredential -Namespace "SSRS"
$rs.Url = "YOUR_SERVER"
$rs.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;
$BICItems = $rs.ListChildren("/", $true);
$BICFolders = $BICItems | Where { $_.TypeName -eq "Folder"}
$BICDataSources = $BICItems | Where {$_.typename -eq "DataSource"}
$BICDataSourceFolders = $BICFolders | Where {$_.path -like "*Data Source*"}
$BICReports = $BICItems | Where {$_.typename -eq "Report"}
foreach ($DataSource in $BICDataSources)
{
$BICDataSourceContents = $rs.GetDataSourceContents($DataSource.Path)
$MyConnectStrings = $BICDataSourceContents | Where {$_.ConnectString -like "*MY_CONNECT_STRING*"}
$MyConnectStrings
}
I don't see a unique identifier in the GetDataSourceContents method to join the report and data source lists.
Nope. Neither do I. However when were are querying for those details we already know something unique enough. The path to the datasource itself. This is also what a report would be using so that should be a good connector.
There is a series of functions that I made to serve this purpose. Find-SSRSEntities, Get-SSRSReportDataSources and Get-SSRSDatasourceDetails are what I will try and showcase here. The last one I just made since I had no reason for those details but it was easy enough to integrate into my module.
Find-SSRSEntities
Return items from a SSRS connection. Supports loads of filtering options.
function Find-SSRSEntities{
[CmdletBinding()]
param(
[Parameter(Position=0,Mandatory=$true)]
[Alias("Proxy")]
[Web.Services.Protocols.SoapHttpClientProtocol]$ReportService,
[Parameter(Position=1)]
[Alias("Path")]
[string]$SearchPath="/",
[Parameter(Position=2)]
[ValidateSet("All", "Folder", "Report", "Resource", "LinkedReport", "DataSource", "Model")]
[Alias("Type")]
[String]$EntityType = "All",
[Parameter(Position=3)]
[String]$Match,
[Parameter(Position=4)]
[Switch]$Partial=$false
)
# Get all of the catalog items that match the criteria passed
# https://msdn.microsoft.com/en-us/library/reportservice2005.reportingservice2005.listchildren.aspx
$recursive = $true
$catalogItems = $ReportService.ListChildren($SearchPath,$recursive)
Write-Verbose "$($catalogItems.Count) item(s) located in the root path $SearchPath"
# Limit the results to the catalog types requested
if($EntityType -ne "All"){$catalogItems = $catalogItems | Where-Object{$_.Type -eq $EntityType}}
Write-Verbose "$($catalogItems.Count) item(s) found matching the type $EntityType"
# Set the match string based on parameters
if(-not $Partial.isPresent -and $Match){$Match = "^$Match$"}
Write-Verbose "Returning all items matching: '$Match'"
# If the regex is an empty string all object will be returned.
return $catalogItems | Where-Object{$_.Name -match $Match}
}
Get-SSRSReportDataSources
When given a valid report path it will return all associated datasources of that report.
function Get-SSRSReportDataSources{
[CmdletBinding()]
param(
[Parameter(Position=0,Mandatory=$true)]
[Alias("Proxy","SSRSService")]
[Web.Services.Protocols.SoapHttpClientProtocol]$ReportService,
[Parameter(Position=1,Mandatory=$true)]
[Alias("Path")]
[string]$ReportPath
)
# Test the report path to be sure it is for a valid report
if(Test-SSRSPath -ReportService $ReportService -EntityPath $ReportPath -EntityType Report){
$ReportService.GetItemDataSources($reportPath) | ForEach-Object{
[pscustomobject][ordered]#{
ReportPath = $reportPath
DataSourceName = $_.name
Reference = $_.item.reference
}
}
} else {
Write-Error "$ReportPath is not a valid report path"
}
}
Get-SSRSDatasourceDetails
When given a valid datasource path it will return all detail of that datasource. Also attaches an extra path property.
function Get-SSRSDatasourceDetails{
[CmdletBinding()]
param(
[Parameter(Position=0,Mandatory=$true)]
[Alias("Proxy")]
[Web.Services.Protocols.SoapHttpClientProtocol]$ReportService,
[Parameter(Position=1,Mandatory=$true,ValueFromPipelineByPropertyName)]
[Alias("Path")]
[string]$EntityPath
)
process{
# Split the path into its folder and entity parts
$SearchPath = Split-SSRSPath $EntityPath -Parent
$EntityName = Split-Path $EntityPath -Leaf
# Verify the path provided is to a valid datasource
if((Find-SSRSEntities -ReportService $ReportService -SearchPath $SearchPath -EntityType DataSource -Match $EntityName -Partial:$false) -as [boolean]){
Add-Member -InputObject ($ReportService.GetDataSourceContents($EntityPath)) -MemberType NoteProperty -Name "Path" -Value $EntityPath -PassThru
} else {
Write-Warning "Could not find a datasource at path: $EntityPath"
}
}
}
So armed with those lets match up all reports in a folder to their datasource connection strings. I would note that all of these functions rely on a active connection to work. Something like this
$ssrsservice = Connect-SSRSService "http://ssrsreports/ReportServer/ReportService2005.asmx" -Credential $credentials
$PSDefaultParameterValues.Add("*SSRS*:ReportService",$ssrsservice)
That will automatically apply the populated -ReportService $ssrsservice to all the SSRS functions I made below.
Else you could just add something like Find-SSRSEntities -ReportService $rs to the code below and it would work.
# Lets get all of the Marketing Datasources
$datasources = Find-SSRSEntities -SearchPath "/data sources/marketing" -EntityType DataSource | Get-SSRSDatasourceDetails
# Now gather all of their reports
Find-SSRSEntities -SearchPath "/Marketing" -EntityType Report |
# Get the report datasources
Get-SSRSReportDataSources | ForEach-Object{
# Attach the connection strings to each object
$reportDataSourceDetail = $_
# Filter the datasource for the individual datasource mapping of this report
$matchingDatasource = $datasources | Where-Object{$_.path -eq $reportDataSourceDetail.Reference}
Add-Member -InputObject $_ -MemberType NoteProperty -Name ConnectionString -Value $matchingDatasource.ConnectString -PassThru
}
This would net me results that look like this:
ReportPath : /Marketing/OandD Class Summary By Month
DataSourceName : Marketing
Reference : /Data Sources/Marketing/Marketing
ConnectionString : Data Source=SQL08R2VM; Initial Catalog=Marketing;
ReportPath : /Marketing/OandD Class YTD Summary
DataSourceName : Marketing
Reference : /Data Sources/Marketing/Marketing
ConnectionString : Data Source=SQL08R2VM; Initial Catalog=Marketing;
These, and other functions, suite me just fine. I have not really had anyone else using them so you might have issues that I have never encountered. Works fine connecting to my SSRS 2008R2 server using PowerShell v5
Here's a T-SQL statement that will return the data source name, path & connection string with the report name and path.
;WITH
XMLNAMESPACES -- XML namespace def must be the first in with clause.
(
DEFAULT 'http://schemas.microsoft.com/sqlserver/reporting/2006/03/reportdatasource'
,'http://schemas.microsoft.com/SQLServer/reporting/reportdesigner'
AS rd
)
,
shared_datasource
AS
(
SELECT
DsnSharedName = sds.[Name]
, DsnPath = sds.[Path]
, DEF = CONVERT(xml, CONVERT(varbinary(max), content))
FROM
dbo.[Catalog] AS sds
WHERE sds.[Type] = 5) --> 5 = Shared Datasource
,
data_source_name (DsnPath, DsnSharedName, DsnConnString)
AS
(
SELECT
cn.DsnPath
, cn.DsnSharedName
, cn.DsnConnString
FROM
(SELECT
sd.DsnPath
, sd.DsnSharedName
, DsnConnString = dsn.value('ConnectString[1]', 'varchar(150)')
FROM
shared_datasource AS sd
CROSS APPLY sd.DEF.nodes('/DataSourceDefinition') AS R(dsn)
) AS cn
)
SELECT
DataSourceName = lk.[Name]
, dsn.DsnPath
, dsn.DsnConnString
, ReportName = c.[Name]
, ReportFolder = c.[Path]
FROM
dbo.[Catalog] c
INNER JOIN dbo.DataSource ds ON c.ItemID = ds.ItemID
INNER JOIN dbo.[Catalog] lk ON ds.Link = lk.ItemID
INNER JOIN data_source_name dsn ON dsn.DsnSharedName = lk.[Name]
WHERE
c.[Type] = 2 --> 2 = Reports
--AND dsn.DsnConnString LIKE '%Initial Catalog%=%DatabaseNameHere%'
Then you can run the T-SQL script file in powershell with this. original post
<# Function to Check whether Server is Ping Status of the Server #>
Function Check-Ping()
{
param
(
[string]$HostName
)
$PingStatus=Get-WmiObject -Query "Select * from Win32_PingStatus where Address='$HostName'"
Return $PingStatus
}
<# Function to Check Instance name Present in the Server #>
Function Get-SQLInstances()
{
param
(
[string]$SQLServerName
)
$Status=Check-Ping($SQLServerName)
if($Status.StatusCode -ne 0)
{
Return "The Server Is Not Reachable"
}
elseif($Status.StatusCode -eq 0)
{
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $SQLServerName)
$RegKey = $Reg.OpenSubKey("SOFTWARE\\Microsoft\\Microsoft SQL Server")
$Instances=$regKey.GetValue("installedinstances")
Return $Instances
}
}
<# Function To Run TSQL and Return Results within HTML Table Tag #>
Function Run-TSQL()
{
Param
(
[string]$MachineName,
[string]$TSQLfilePath
)
$Assembly=[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
$Instances=Get-SQLInstances($MachineName)
$TSQL=Get-Content $TSQLfilePath
foreach($Instance in $Instances)
{
$SQLServiceStatus=Get-Service -ComputerName $MachineName | Where-Object {$_.displayname -like "SQL Server ("+$Instance+")"}
if($SQLServiceStatus.Status -eq "Running")
{
if($Instance -eq "MSSQLSERVER")
{
$SQLServer=$MachineName
}
Else
{
$SQLServer=$MachineName+"\"+$Instance
}
$SQLServerObject = new-Object Microsoft.SqlServer.Management.Smo.Server($SQLServer)
$DatabaseObject = New-Object Microsoft.SqlServer.Management.Smo.Database
$DatabaseObject = $SQLServerObject.Databases.Item("Master")##The TSQL Script Runs in Master Database
$OutPutDataSet = $DatabaseObject.ExecuteWithResults($TSQL)
for($t=0;$t -lt $OutPutDataSet.Tables.Count;$t++)
{
$OutString+="<Table Border=2>"
$OutString+="<Tr>"
foreach($Column in $OutPutDataSet.Tables[$t].Columns)
{
$OutString+="<Th>"
$OutString+=$Column.ColumnName
$OutString+="</Th>"
}
$OutString+="</Tr>"
for($i=0;$i -lt $OutPutDataSet.Tables[$t].Rows.Count;$i++)
{
$OutString+="<Tr>"
for($j=0;$j -lt $OutPutDataSet.Tables[$t].Columns.Count;$j++)
{
$OutString+="<Td>"
$OutString+=$($OutPutDataSet.Tables[$t].Rows[$i][$j])
$OutString+="</Td>"
}
$OutString+="</Tr>"
}
$OutString+="</Table>"
$OutString+="</Br>"
$OutString+="</Br>"
}
}
}
Return $OutString
}
<# Function To Add Table Tag to with In HTML tags
Modify Title and Subject as Per yoru Requirement
#>
Function Get-HTMLOut()
{
Param
(
[String]$InputFile,
[String]$OutputFile,
[String]$TSQL
)
$Out+="<Html>"
$Out+="<Title>Run TSQL and Return HTML FIle</Title>" ## Modify 'TiTle' Tag as per your Required
$Out+="<Head><style>body {background-color:lightgray} H3{color:blue}H1{color:green}table, td, th {border: 1px solid green;}th {background-color: green;color: white;}</style></Head>" ## Modify 'Head' Tag as per your Required
$Out+="<Body><H1 Align='Center'>Run TSQL and Return HTML File</H1></Br></Br>" ## Modify 'Body' Tag as per your Required
ForEach($ServerName in Get-Content $InputFile)
{
$Out+="<H3 align='center'>--------------$ServerName--------------</H3>" ## Modify 'header Text' Tag as per your Required
$Out+="</Br>"
$Out+=Run-TSQL -MachineName $ServerName -TSQLfilePath $TSQL
}
$Out+="</Body></Html>"
Set-Content -Value $Out -Path $OutputFile
}
<# Call Get-HTMLOut Function
It Accepts 3 parameter
a. -InputFile (.txt file each server in a List withOut Instance Name)
b. -OutputFile (.Html File to which Output need to be sent)
c. -TSQL (.sql file which Contains the Script to Run)
#>
Get-HTMLOut -InputFile ".\Servers.txt" -OutputFile .\Status.Html -TSQL '.\TSQL Script.sql'

How to differentiate not set parameter from $false, 0, empty string?

I have function that updates object in WMI. I want user to be able to specify in parameters only values that he wants to update. How can I do it?
function UpdateObject ([bool] $b1, [bool] $b2, [int] $n1, [string] $s1)
{
$myObject = GetObjectFromWmi #(...)
#(...)
#This is bad. As it overrides all the properties.
$myObject.b1 = $b1
$myObject.b2 = $b2
$myObject.n1 = $n1
$myObject.s1 = $s1
#This is what I was thinking but don't kwow how to do
if(IsSet($b1)) { $myObject.b1 = $b1 }
if(IsSet($b2)) { $myObject.b2 = $b2 }
if(IsSet($n1)) { $myObject.n1 = $n1 }
if(IsSet($s1)) { $myObject.s1 = $s1 }
#(...) Store myObject in WMI.
}
I tried passing $null as as parameter but it get's automaticly converted to $false for bool, 0 for int and empty string for string
What are your suggestions?
Check $PSBoundParameters to see if it contains a key with the name of your parameter:
if($PSBoundParameters.ContainsKey('b1')) { $myObject.b1 = $b1 }
if($PSBoundParameters.ContainsKey('b2')) { $myObject.b2 = $b2 }
if($PSBoundParameters.ContainsKey('n1')) { $myObject.n1 = $n1 }
if($PSBoundParameters.ContainsKey('s1')) { $myObject.s1 = $s1 }
$PSBoundParameters acts like a hashtable, where the keys are the parameter names, and the values are the parameters' values, but it only contains bound parameters, which means parameters that are explicitly passed. It does not contain parameters filled in with a default value (except for those passed with $PSDefaultParameterValues).
Building on briantist's answer, if you know that all the parameters exist as properties on the target object you can simply loop through the $PSBoundParameters hashtable and add them one by one:
foreach($ParameterName in $PSBoundParameters.Keys){
$myObject.$ParameterName = $PSBoundParameters[$ParameterName]
}
If only some of the input parameters are to be passed as property values, you can still specify the list just once, with:
$PropertyNames = 'b1','b2','n1','s1'
foreach($ParameterName in $PSBoundParameters.Keys |Where-Object {$PropertyNames -contains $_}){
$myObject.$ParameterName = $PSBoundParameters[$ParameterName]
}
To save yourself having to create a parameter for each property you may want to change, consider using a hashtable or other object to pass this information to your function.
For example:
function UpdateObject ([hashtable]$properties){
$myObject = GetObjectFromWmi
foreach($property in $properties.Keys){
# without checking
$myObject.$property = $properties.$property
# with checking (assuming members of the wmiobject have MemberType Property.
if($property -in (($myObject | Get-Member | Where-Object {$_.MemberType -eq "Property"}).Name)){
Write-Output "Updating $property to $($properties.$property)"
$myObject.$property = $properties.$property
}else{
Write-Output "Property $property not recognised"
}
}
}
UpdateObject -properties {"b1" = $true; "b2" = $false}
If you want a [Boolean] parameter that you want the user to specify explicitly or omit (rather than a [Switch] parameter which can be present or not), you can use [Nullable[Boolean]]. Example:
function Test-Boolean {
param(
[Nullable[Boolean]] $Test
)
if ( $Test -ne $null ) {
if ( $Test ) {
"You specified -Test `$true"
}
else {
"You specified -Test `$false"
}
}
else {
"You did not specify -Test"
}
}
In this sample function the $Test variable will be $null (user did not specify the parameter), $true (user specified -Test $true), or $false (user specified -Test $false). If user specifies -Test without a parameter argument, PowerShell will throw an error.
In other words: This gives you a tri-state [Boolean] parameter (missing, explicitly true, or explicitly false). [Switch] only gives you two states (present or explicitly true, and absent or explicitly false).

How can I pass dynamic parameters to powershell script and iterate over the list?

I want to create a powershell script that accepts dynamic parameters and I also want to iterate through them.
eg:
I call the powershell script in the following manner.
ParametersTest.ps1 -param1 value1 -param2 value2 -param3 value3
And I should be able to access my params inside the script as follows:
for($key in DynamicParams) {
$paramValue = DynamicParams[$key];
}
Is there anyway to do this in powershell? Thanks in advance.
There is nothing built-in like that (essentially you're asking for PowerShell parameter parsing in the absence of any definition of those parameters). You can emulate it, though. With $args you can get at all arguments of the function as an array. You can then iterate that and decompose it into names and values:
$DynamicParams = #{}
switch -Regex ($args) {
'^-' {
# Parameter name
if ($name) {
$DynamicParams[$name] = $value
$name = $value = $null
}
$name = $_ -replace '^-'
}
'^[^-]' {
# Value
$value = $_
}
}
if ($name) {
$DynamicParams[$name] = $value
$name = $value = $null
}
To iterate over dynamic parameters you can either do something like you wrote
foreach ($key in $DynamicParams.Keys) {
$value = $DynamicParams[$key]
}
(note the foreach, not for, the latter of which cannot work like you wrote it) or just iterate normally over the hash table:
$DynamicParams.GetEnumerator() | ForEach-Object {
$name = $_.Key
$value = $_.Value
}