Compound Dynamic Parameters in Powershell - powershell

I am attempting to use two Dynamic Parameters in the same cmdlet. The problem is that I would like the second dynamic parameter to use the first parameter to populate the result set.
For example the syntax for using the command could be something like this -
Get-MSRP -Manufacturer Jeep -Trim Rubicon
'Manufacturer' is a dynamic parameter that looks at a file on the disk to populate values for the user. I would 'Trim' to consume the 'Manufacturer' option that the user chose to create a result set of 'Trim'.
My 'Manufacturer' is working correctly but I believe that the value which the user has chosen isn't available when the code for the DynamicParam I've created for 'Trim', has been ran.
Any help?
function Get-MSRP{
[cmdletbinding()]
param()
DynamicParam{
$Param1 = "Manufacturer",0,{GC c:\temp\manufacturers.txt},$False
$Param2 = "Trim",1,{GC c:\temp\$Manufacturer\Trim.txt},$False
Get-DynamicParameterSet $Param1,$Param2
}
begin{
$Manufacturer = $PSBoundParameters["Manufacturer"]
$Trim = $PSBoundParameters["Trim"]
}
process{
return Invoke-Sqlcmd -Database 'db' -ServerInstance 'server' -Query
"Select MSRP from pricing.MSRP where Manufacturer = '$Manufacturer'
and Trim = '$Trim'"
}
}
function Get-DynamicParameterSet{
param($Params)
$RuntimeParameterDictionary = New-Object
System.Management.Automation.RuntimeDefinedParameterDictionary
if($Params[0].GetType().Name -eq "String" ){
Write-Debug "Single Param to build"
$RuntimeParameterDictionary = BuildSet $Params[0] $Params[1]
$([Scriptblock]$Params[2]) $Params[3] $Params[4] $RuntimeParameterDictionary
}else{
foreach($Param in $Params){
$RuntimeParameterDictionary = BuildSet $Param[0] $Param[1]
$([Scriptblock]$Param[2]) $Param[3] $Param[4]
$RuntimeParameterDictionary
}
}
return $RuntimeParameterDictionary
}
function BuildSet{
param(
$ParameterName,
[int]$Position,
[ScriptBlock]$scriptBlock,
$Mandatory,
$SetNames,
[System.Management.Automation.RuntimeDefinedParameterDictionary]$RuntimeParameterDictionary)
Write-Debug "Setting up $ParameterName"
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.Mandatory = $Mandatory
$ParameterAttribute.Position = $Position
$ParameterAttribute.ParameterSetName = $SetNames
$AttributeCollection.Add($ParameterAttribute)
Write-Debug "Generating result set"
$arrSet = Invoke-Command $scriptBlock
Write-Debug "Generated as $arrSet"
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
$AttributeCollection.Add($ValidateSetAttribute)
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
$RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
return $RuntimeParameterDictionary
}
Minimal & Verifiable
function Get-MSRP{
[cmdletbinding()]
param()
DynamicParam{
$Param1 = "Manufacturer",0,{GC c:\temp\manufacturers.txt},$False
$Param2 = "Trim",1,{gci c:\temp\$Manufacturer},$False
Get-DynamicParameterSet $Param1,$Param2
}
begin{
$Manufacturer = $PSBoundParameters["Manufacturer"]
$Trim = $PSBoundParameters["Trim"]
}
process{
return Invoke-Sqlcmd -Database 'db' -ServerInstance 'server' -Query
"Select MSRP from pricing.MSRP where Manufacturer = '$Manufacturer'
and Trim = '$Trim'"
}
}
function Get-DynamicParameterSet{
param($Params)
$RuntimeParameterDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
foreach($Param in $Params){
$ParameterName = $Param[0]
[int]$Position = $Param[1]
[ScriptBlock]$scriptBlock = $([Scriptblock]$Param[2])
$Mandatory = $Param[3]
$SetNames =$Param[4]
$AttributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$ParameterAttribute = [System.Management.Automation.ParameterAttribute]::new()
$ParameterAttribute.Mandatory = $Mandatory
$ParameterAttribute.Position = $Position
$ParameterAttribute.ParameterSetName = $SetNames
$AttributeCollection.Add($ParameterAttribute)
$arrSet = Invoke-Command $scriptBlock
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
$AttributeCollection.Add($ValidateSetAttribute)
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
$RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
}
return $RuntimeParameterDictionary
}

After some research, i've found that you cannot have this type of 'Compound dynamic parameter' in powershell where the second dynamic parameter uses a variable output from the users first dynamic parameter. The reason being that the values aren't bound to dynamic parameter until the command is ran.
Another note on Dynamic parameters, the ValueFromPipeline order is not respected when using them!

Related

Powershell Gui not returning SQL query results

I'm building a simple GUI where support staff can look up basic user information. The GUI is supposed to pull this data from an SQL DB, but nothing happens when the button is pressed.
function SQLquery {
param (
[Parameter(Mandatory = $true)]
[ValidateSet('NemOrgDB')]
[string]
$System,
[string]
$Query
)
switch ($System) {
NemOrgDB { $ConnectionString = 'Place holder for Stack Overflow' }
}
#New SqlConnection object
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection
$sqlConnection.ConnectionString = $ConnectionString
$sqlConnection.Open()
#New Sqlcommand object
$sqlCommand = New-Object System.Data.SqlClient.SqlCommand
$sqlCommand.Connection = $sqlConnection
$sqlCommand.CommandText = $Query
#Create new sqldataadapter object
$sqlDataAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$sqlDataAdapter.SelectCommand = $sqlCommand
#Create new dataset object
$DataSet = New-Object System.Data.DataSet
#Fill dataset with dataadapter input
try {
$sqlDataAdapter.Fill($DataSet) | Out-Null
$output = $DataSet.Tables
}
catch {
$output = $null
}
#Close sql connection
$sqlConnection.close()
$null = $sqlConnection
$output
}
Here is the Function in use:
#Søgnigns criteria
$EmployeeID = Read-Host -Prompt "What user to find?"
#SQL Query
SQLquery -System NemOrgDB -Query "Select [medarbejder_wnr]
,[navn]
,[stilling]
,[stilling_nr]
,[firmakode_txt]
FROM [PersonData_NemOrg].[dbo].[Personale]
WHERE medarbejder_wnr ='$EmployeeID';"
The 2 pieces of code work as they should in a console environment with no Gui attached, they return the following results:
EmployeeID: EmployeeID
Name: Name
Role: IT support
Role number : Number
fimakode_txt : Company
When the same code is run inside the GUI nothing happens, here is the GUI code:
#ButtonSearch
#
$ButtonSearch.BackColor = [System.Drawing.SystemColors]::ActiveBorder
$ButtonSearch.FlatStyle = [System.Windows.Forms.FlatStyle]::System
$ButtonSearch.Font = (New-Object -TypeName System.Drawing.Font -ArgumentList #([System.String]'Tahoma',[System.Single]9,[System.Drawing.FontStyle]::Bold,[System.Drawing.GraphicsUnit]::Point,([System.Byte][System.Byte]0)))
$ButtonSearch.Location = (New-Object -TypeName System.Drawing.Point -ArgumentList #([System.Int32]12,[System.Int32]118))
$ButtonSearch.Name = [System.String]'ButtonSearch'
$ButtonSearch.Size = (New-Object -TypeName System.Drawing.Size -ArgumentList #([System.Int32]150,[System.Int32]23))
$ButtonSearch.TabIndex = [System.Int32]24
$ButtonSearch.Text = [System.String]'Search'
$ButtonSearch.UseCompatibleTextRendering = $true
$ButtonSearch.UseVisualStyleBackColor = $false
$ButtonSearch.add_Click($ButtonSearch_Click)
#
AND
$ButtonSearch_Click = {
$Wnummer = $TextBoxWnummer1.Text
SQLquery -System NemOrgDB -Query "Select [medarbejder_wnr]
,[navn]
,[stilling]
,[stilling_nr]
,[firmakode_txt]
FROM [PersonData_NemOrg].[dbo].[Personale]
WHERE medarbejder_wnr ='$Wnummer';"
}
It simply returns nothing.

Dynamic Parameter cannot be specified in parameter set '__AllParameterSets'

Hello, I am trying to create a dynamic ValidateSet that is based on the contents of a simple text file for one of my cmdlet parameters. I followed this blog post https://blogs.technet.microsoft.com/pstips/2014/06/09/dynamic-validateset-in-a-dynamic-parameter/, and I came up with the following:
function Remove-NetScalerWhiteListItem
{
[CmdletBinding()]
Param
(
)
DynamicParam
{
$ParameterName = "ServiceGroup"
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.Mandatory = $true
$ParameterAttribute.Position = 0
$ParameterAttribute.DontShow = $false
$serviceGroups = Get-NetScalerWhiteList
$ValidateSetAtrribute = New-Object System.Management.Automation.ValidateSetAttribute($serviceGroups)
$AttributeCollection.Add($ValidateSetAtrribute)
$RunTimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
$RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$RuntimeParameterDictionary.Add($ParameterName, $RunTimeParameter)
$RuntimeParameterDictionary
}
Begin
{
$ServiceGroup = $PSBoundParameters[$ParameterName]
}
Process
{
Copy-Item "$masterIgnoreFilePath\ingnore.txt" "$masterIgnoreFilePath\ingnore.bak"
$serviceGroups = Get-NetScalerWhiteList
$serviceGroups.Remove($serviceGroup)
Write-Host $serviceGroups
}
}
This partially works, if I begin by typing Remove-NetScalerWhiteListItem -ServiceGroup my validation set is there and working, however when I select one of the items and run the command I get the following error:
Remove-NetScalerWhiteListItem : Parameter 'ServiceGroup' cannot be specified
in parameter set '__AllParameterSets'.
At line:1 char:1
+ Remove-NetScalerWhiteListItem -servicegroup servicegroupname
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Remove-
NetScalerWhiteListItem], ParameterBindingException
+ FullyQualifiedErrorId : ParameterNotInParameterSet,Remove-
NetScalerWhiteListItem
As for the line $serviceGroups = Get-NetScalerWhiteList that is just a wrapper around a Get-Content call to a specific file.
I think you need one more line. You never add the $ParameterAttribute to the $AttributeCollection. You can do so by using this line $AttributeCollection.Add($ParameterAttribute).
function Remove-NetScalerWhiteListItem
{
[CmdletBinding()]
Param
(
)
DynamicParam
{
$ParameterName = "ServiceGroup"
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.Mandatory = $true
$ParameterAttribute.Position = 0
$ParameterAttribute.DontShow = $false
$serviceGroups = Get-NetScalerWhiteList
$ValidateSetAtrribute = New-Object System.Management.Automation.ValidateSetAttribute($serviceGroups)
$AttributeCollection.Add($ValidateSetAtrribute)
$AttributeCollection.Add($ParameterAttribute)
$RunTimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
$RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$RuntimeParameterDictionary.Add($ParameterName, $RunTimeParameter)
$RuntimeParameterDictionary
}
Begin
{
$ServiceGroup = $PSBoundParameters[$ParameterName]
}
Process
{
Copy-Item "$masterIgnoreFilePath\ingnore.txt" "$masterIgnoreFilePath\ingnore.bak"
$serviceGroups = Get-NetScalerWhiteList
$serviceGroups.Remove($serviceGroup)
Write-Host $serviceGroups
}
}

Access datarow from a workflow

I have a script which look like below:
function Invoke-SQL {
param(
[string] $dataSource = ".\MSSQLSERVER",
[string] $database = "master",
[string] $sqlCommand = $(throw "Please specify a query.")
)
$connectionString = "Data Source=$dataSource; Integrated Security=True; Initial Catalog=$database; Connect Timeout=100"
$connection = new-object system.data.SqlClient.SQLConnection($connectionString)
$command = new-object system.data.sqlclient.sqlcommand($sqlCommand,$connection)
Try {
$connection.Open()
} catch {
return, $null
}
$adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command
$dataset = New-Object System.Data.DataSet
$adapter.Fill($dataSet) | Out-Null
$connection.Close()
$dataSet.Tables
}
[string]$SQL = "select CmsSrvName from CmsServerList_VM"
$DbInstances = Invoke-SQL "DBSRV" "Test" $SQL
workflow wf1{
Param([System.Data.DataTable]$instance)
ForEach -Parallel -ThrottleLimit 10 ($i in $instance) {
InlineScript{
$t = $using:i
Write-Verbose "$t['CmsSrvName']"
}
}
}
wf1 -Verbose $DbInstances
Output:
VERBOSE: [localhost]:System.Data.DataRow['CmsSrvName']
VERBOSE: [localhost]:System.Data.DataRow['CmsSrvName']
VERBOSE: [localhost]:System.Data.DataRow['CmsSrvName']
The output is not what I expected, it just print out the type name not the value. How can I access the DataRow value in a workflow?(in Powershell 5)
Thanks in advance for the help

Nesting dynamic parameters in PowerShell

I am working on a function that will insert a row into a SQL database. It is basically a simple change log to help me track what is changed on my various SQL instances. As part of this, I want to have the following parameters:
Timestamp
Server
Instance
Change
I've got the Timestamp, Change, and Server all figured out, but the Instance is giving me some trouble. The Server parameter is dynamic, as it pulls a list of SQL servers from my inventory. I then want the value of that parameter to be used in another dynamic parameter, which pulls a list of the instances that are on that server (also from my inventory). Here is what I have for the dynamic portion:
DynamicParam {
if (!(Get-Module sqlps)){ Pop-Location; Import-Module sqlps -DisableNameChecking; Push-Location }
$inventoryinstance = 'ServerName'
$newparams = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$server_query = 'SELECT [Name] FROM [ServerInventory].[dbo].[Servers] WHERE [TypeID] = 1 ORDER BY [Name]'
$servers = Invoke-Sqlcmd -serverinstance $inventoryinstance -query $server_query -connectiontimeout 5
# Populate array
$serverlist = #()
foreach ($servername in $servers.Name) {
$serverlist += $servername
}
$attributes = New-Object System.Management.Automation.ParameterAttribute
$attributes.ParameterSetName = "__AllParameterSets"
$attributes.Position = 1
$attributes.Mandatory = $true
$attributes.HelpMessage = "The server the change was made on"
# Server list parameter setup
if ($serverlist){ $servervalidationset = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $serverlist }
$serverattributes = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$serverattributes.Add($attributes)
if ($serverlist){ $serverattributes.Add($servervalidationset) }
$serverob = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Server", [String], $serverattributes)
$newparams.Add("Server", $serverob)
$instance_query = "SELECT [Name] FROM [ServerInventory].[dbo].[SQLInstances] WHERE [ServerID] = (SELECT [ServerID] FROM [ServerInventory].[dbo].[Servers] WHERE [Name] = '$($PSBoundParameters.Server)')"
$instances = Invoke-Sqlcmd -serverinstance $inventoryinstance -query $instance_query -connectiontimeout 5
# Populate array
$instancelist = #()
foreach ($instancename in $instances.Name) {
$instancelist += $instancename
}
$attributes = New-Object System.Management.Automation.ParameterAttribute
$attributes.ParameterSetName = "__AllParameterSets"
$attributes.Position = 2
$attributes.Mandatory = $false
$attributes.HelpMessage = "The instance the change was made on, do not specify for server-level changes"
# Server list parameter setup
if ($instancelist){ $instancevalidationset = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $instancelist }
$instanceattributes = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$instanceattributes.Add($attributes)
if ($instancelist){ $instanceattributes.Add($instancevalidationset) }
$instanceob = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Instance", [String], $instanceattributes)
$newparams.Add("Instance", $instanceob)
return $newparams
}
Everything seems to be working, except the value for the instance variable doesn't autocomplete. Is it possible to use the value of one dynamic parameter to generate another?

Better way to validate when not using a function ValidateSet

I'm looking for a better way to validate a "response" from a user for my script. I know you can use a function to validate this but I want it to be more interactive.
$DPString = #"
Enter users department.
Valid choices are;
"Accounts","Claims","Broker Services","Underwriting","Compliance","HR","IT","Developmet","Legal" and "Legal Underwriting"
"#
$Department = Read-Host "$DPString"
do
{
Switch ($Department)
{
Accounts { $DepBool = $true }
Claims { $DepBool = $true }
"Broker Services" { $DepBool = $true }
Underwriting { $DepBool = $true }
Compliance { $DepBool = $true }
"Legal Underwriting" { $DepBool = $true }
Legal { $DepBool = $true }
HR { $DepBool = $true }
IT { $DepBool = $true }
Development { $DepBool = $true }
Default { $DepBool = $false }
}
if ($DepBool -eq $true)
{
$DepLoop = $false
}
else {
$Department = Read-Host "Please enter a valid Department"
$DepLoop = $true
}
}
while ($DepLoop)
It's not clear in what context the user is being prompted for input, but I would favour a list of valid parameters being passed in on the command line. I would make them mutually exclusive using Parameter Sets.
However, if this is a part of a larger script which prompts for input part way through, then it might be appropriate to prompt for input using the windows API for displaying an input box. Here's a link which describes this approach in more detail Creating a Custom Input Box.
Although it feels wrong to display UI from powershell, I understand that there are times when this is desirable, using the link above here's an implementation of a ListBox, you simply pass it a string array and it returns the selected value:
<#
.SYNOPSIS
Displays a Windows List Control and returns the selected item
.EXAMPLE
Get-ListBoxChoice -Title "Select Environment" -Prompt "Choose an environment" -Options #("Option 1","Option 2")
This command displays a list box containing two options.
.NOTES
There are two command buttons OK and Cancel, selecting OK will return the selected option, whilst
Cancel will return nothing.
.RELATED LINKS
http://technet.microsoft.com/en-us/library/ff730941.aspx
#>
Function Get-ListBoxChoice
{
[cmdletbinding()]
param
(
[Parameter(Mandatory=$true)]
[string] $Title,
[Parameter(Mandatory=$true)]
[string] $Prompt,
[Parameter(Mandatory=$true)]
[string[]] $Options
)
Write-Verbose "Get-ListBoxChoice"
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
$uiForm = New-Object System.Windows.Forms.Form
$uiForm.Text = $Title
$uiForm.FormBorderStyle = 'Fixed3D'
$uiForm.MaximizeBox = $false
$uiForm.Size = New-Object System.Drawing.Size(300,240)
$uiForm.StartPosition = "CenterScreen"
$uiForm.KeyPreview = $True
$uiForm.Add_KeyDown({if ($_.KeyCode -eq "Enter")
{$chosenValue=$objListBox.SelectedItem;$uiForm.Close()}})
$uiForm.Add_KeyDown({if ($_.KeyCode -eq "Escape")
{$uiForm.Close()}})
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,160)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Add_Click({$chosenValue=$objListBox.SelectedItem;$uiForm.Close()})
$uiForm.Controls.Add($OKButton)
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150,160)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
$CancelButton.Add_Click({$uiForm.Close()})
$uiForm.Controls.Add($CancelButton)
$uiLabel = New-Object System.Windows.Forms.Label
$uiLabel.Location = New-Object System.Drawing.Size(10,20)
$uiLabel.Size = New-Object System.Drawing.Size(280,20)
$uiLabel.Text = $Prompt
$uiForm.Controls.Add($uiLabel)
$objListBox = New-Object System.Windows.Forms.ListBox
$objListBox.Location = New-Object System.Drawing.Size(10,40)
$objListBox.Size = New-Object System.Drawing.Size(260,20)
$objListBox.Height = 120
$Options | % {
[void] $objListBox.Items.Add($_)
}
$uiForm.Controls.Add($objListBox)
$uiForm.Topmost = $True
$uiForm.Add_Shown({$uiForm.Activate()})
[void] $uiForm.ShowDialog()
$chosenValue
}
If they're running at least V3, you can use Out-Gridview:
$Departments = #(
[PSCustomObject]#{Name = 'Accounts';Description = 'Accounting Department'}
[PSCustomObject]#{Name = 'Claims';Description = 'Claims Department'}
[PSCustomObject]#{Name = 'Broker';Description = 'Broker Services'}
)
$GridParams = #{
Title = "Select a department, and press 'OK', or 'Cancel' to quit."
OutPutMode = 'Single'
}
$Department = $Departments | Out-Gridview #GridParams
If ($Department)
{ #Do stuff }