Dynamically adjust code based on a condition - powershell

As a Powershell learner, I ran into -f format operator.
I then thought to myself, is it possible to dynamically change code based on a condition. Take for example a mapping drive scenario:
$creds = Get-Credential
$share_needs_creds = $true
$drive_map = New-Object -ComObject WScript.Network
if ($share_needs_creds){
$drive_map.MapNetworkDrive('Z:', '\\server\share', $false, "$($creds.Username)", $($creds.GetNetworkCredential().Password)")
} else {
$drive_map.MapNetworkDrive('Z:', '\\server\share', $false)
}
can this be re-written as follows:
$creds = Get-Credential
$share_needs_creds = $true
$drive_map = New-Object -ComObject WScript.Network
if ($share_needs_creds){
$condition_smart = ', "$($creds.Username)", $($creds.GetNetworkCredential().Password)"'
} else {
$condition_smart = ''
}
$drive_map.MapNetworkDrive('Z:', '\\server\share', $false{0}) -f $condition_smart
Any advise is appreciated!

That's not going to work - the -f operator returns exactly 1 string as output - and you need to pass a variable number of arguments to the parameter list of the method you're calling.
If you want to invoke a method dynamically on a ComObject, prepare your argument list as an array, and pass it to .Invoke() on the method name exposed by PowerShell's type adapter:
$arguments = #(
'Z:', '\\server\share', $false
if($share_needs_creds){
$creds.Username, $creds.GetNetworkCredential().Password
}
)
$drive_map.MapNetworkDrive.Invoke($arguments)

Related

powershell if statements not giving predicted outcome

I'm new to coding trying to figure out powershell and confused at If statements.
Trying to get this code so it'll create the job scheduling to run on login but it won't create it on the initial first run. I want to add in an if statement at the end to run the command to check if it exists on a desktop as some end users I want to deploy this too will already have it. But I'm unsure where the error lies with the code and how to address or simplify the code?
Any help would be greatly appreciated
$PathTest = Test-Path -Path "C:\Users\public\desktop\Google.lnk"
$Google = {$ShortcutPath = "C:\users\public\desktop\Google.lnk"
$IconLocation = "C:\windows\System32\SHELL32.dll"
$IconArrayIndex = 150
$Shortcut = $Shell.CreateShortcut($ShortcutPath)
$Shortcut.TargetPath = "http://www.google.com"
$Shortcut.IconLocation = "$IconLocation, $IconArrayIndex"
$Shortcut.Save()
}
$Trigger = New-JobTrigger -AtLogOn -RandomDelay 00:00:30
Register-ScheduledJob -Trigger $Trigger -RunAs32 -ScriptBlock {$Google} -Name Google
If ($PathTest -is True) {
$PathTest
} Else {
$Google
}
You use a wrong if operator:
-is is used for checking types:
if ( $value -is [type] )
{
# do something
}
For a boolean check you can just use:
$condition = $true
if ( $condition ) # check for $true
{
Write-Output "The condition was true"
}
$condition = $false
if ( !$condition ) # check for $false
{
Write-Output "The condition was also true by inverting with not operator"
}
MS Docs PowerShell IF-Statement
The if statement is using wrong operator. -is is used to test object types, not logical equality which is -eq. In addition, the boolean true is $true

How to implement `DynamicParam` through dynamic parameters?

Assumption:
$a = 1,2,3
if $a =1,2
Intent:Display parameter b
PS >test -<tab>
a
PS >test -a 1 -<tab>
b
PS >test -a 3 -<tab>
PS >test -a 3
How to achieve the following intent
Function test {
[CmdletBinding()]
Param()
DynamicParam {
parameter a
Dynamic parameter b (a is 1 =$true)
Dynamic parameter c (b is 3)
???
}
}
You don't have to assume. Research it.
What Dynamics Params are and how to use them if a fully documented use case via the MS docs site and many blog posts.
Searching for 'powershell dynamics parameters', will give you a solid list. Be sure to read the details, so as to limit any confusion about their use case.
Examples:
How can I pass dynamic parameters to powershell script and iterate
over the list?
Cmdlet dynamic parameters
PowerShell Deep Dive: Discovering dynamic parameters
Dynamic Parameters in PowerShell
How To Implement Dynamic Parameters in Your PowerShell Functions
enter link description here
# Example from the above link.
function Get-ConfigurationFile
{
[OutputType([System.IO.FileInfo])]
[CmdletBinding()]
param
()
DynamicParam
{
$ParamAttrib = New-Object System.Management.Automation.ParameterAttribute
$ParamAttrib.Mandatory = $true
$ParamAttrib.ParameterSetName = '__AllParameterSets'
$AttribColl = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$AttribColl.Add($ParamAttrib)
$configurationFileNames = Get-ChildItem -Path 'C:\ConfigurationFiles' | Select-Object -ExpandProperty Name
$AttribColl.Add((New-Object System.Management.Automation.ValidateSetAttribute($configurationFileNames)))
$RuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter('FileName', [string], $AttribColl)
$RuntimeParamDic = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$RuntimeParamDic.Add('FileName', $RuntimeParam)
return $RuntimeParamDic
}
process
{
$configFileFolder = 'C:\ConfigurationFiles'
Get-ChildItem -Path $configFileFolder -Filter "$($PSBoundParameters.FileName).txt"
}
}
As for this...
As you can see,tabs cannot return a parameter
..., what you are showing is not the way this use case works.
Based on what you seem to be after, this sampler should get you there. please read the whole article for a better understanding.
Dynamic Parameters in PowerShell
Function Get-Order {
[CmdletBinding()]
Param(
[Parameter(
Mandatory=$true,
Position=1,
HelpMessage="How many cups would you like to purchase?"
)]
[int]$cups,
[Parameter(
Mandatory=$false,
Position=2,
HelpMessage="What would you like to purchase?"
)]
[ValidateSet("Lemonade","Water","Tea","Coffee")]
[string]$product="Lemonade"
)
Process {
$order = #()
for ($cup = 1; $cup -le $cups; $cup++) {
$order += "$($cup): A cup of $($product)"
}
$order
}
}
Or this one...
Using Dynamic Parameters
function Test-Department
{
[CmdletBinding()]
param
(
[Parameter(Mandatory=$true)]
[ValidateSet('Microsoft','Amazon','Google','Facebook')]
$Company
)
dynamicparam
{
# this hash table defines the departments available in each company
$data = #{
Microsoft = 'CEO', 'Marketing', 'Delivery'
Google = 'Marketing', 'Delivery'
Amazon = 'CEO', 'IT', 'Carpool'
Facebook = 'CEO', 'Facility', 'Carpool'
}
# check to see whether the user already chose a company
if ($Company)
{
# yes, so create a new dynamic parameter
$paramDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
$attributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]
# define the parameter attribute
$attribute = New-Object System.Management.Automation.ParameterAttribute
$attribute.Mandatory = $false
$attributeCollection.Add($attribute)
# create the appropriate ValidateSet attribute, listing the legal values for
# this dynamic parameter
$attribute = New-Object System.Management.Automation.ValidateSetAttribute($data.$Company)
$attributeCollection.Add($attribute)
# compose the dynamic -Department parameter
$Name = 'Department'
$dynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter($Name,
[string], $attributeCollection)
$paramDictionary.Add($Name, $dynParam)
# return the collection of dynamic parameters
$paramDictionary
}
}
end
{
# take the dynamic parameters from $PSBoundParameters
$Department = $PSBoundParameters.Department
"Chosen department for $Company : $Department"
}
}
As well as this step by step one with full explanations...
How To: Add Dynamic Parameters to Your PowerShell Functions
As noted in my initial response, there are plenty of examples of this use case, but it requires study to fully grasp the concept, and how to use it/them.

Why does this new JObject strangely change my string when adding to it?

I have a PowerShell script, whose objective is to convert an incoming JSON object into a different sort of JSON object. (The "why" falls outside of the scope of this question.) The script (so far) is below (URL is obfuscated and will not work):
function Map-TempStudents {
param($uffStudent) # JObject containing UFF-formatted student
[String]$districtCode = $uffStudent.Item("DistrictCode").ToString()
Write-Host "districtCode = " $districtCode
$tempStudent = New-Object -TypeName Newtonsoft.Json.Linq.JObject
$tempStudent.Add("DistrictNum", $uffStudent.Item("DistrictCode").ToString())
Write-Host "tempStudent(DistrictNum) = " $tempStudent.Item("DistrictNum").ToString()
}
$currentPath = Split-Path -parent $MyInvocation.MyCommand.Definition
$jsonNetPath = $currentPath + '\Newtonsoft.Json.dll'
Add-Type -Path $jsonNetPath
$wc = New-Object system.Net.WebClient;
$studentPersonal = $wc.downloadString("http://test.foo.org/StudentPersonal")
$json = [Newtonsoft.Json.Linq.JObject]::Parse($studentPersonal)
$studentPersonal | Out-File "test.txt" # DEBUG
$studentBases = $json.Item("Data").Item("AssignTeacher")
Map-TempStudents -uffStudent $studentBases[0]
Running this script gives the following output:
districtCode = 0745
tempStudent(DistrictNum) = 485
To be clear, "0745" is the correct value, and "485" is the unexpected value.
I would expect tempStudent(DistrictNum) to be "0745" as well.
Why would this value change like this?
I was able to reproduce your issue with the following script (assuming Newtonsoft.Json.dll is already loaded into PowerShell):
$jo = New-Object -TypeName Newtonsoft.Json.Linq.JObject
$jo.Add("foo", "0745")
Write-Host $jo.Item("foo").ToString()
The Add method on JObject accepts an object as its second argument. If you pass a numeric string with a leading zero (and all the digits are less than 8), it is interpreted as an octal number. Hence "0745" gets converted into "485". This must be a PowerShell thing, because the equivalent code in C# works correctly:
JObject jo = new JObject();
jo.Add("foo", "0745");
Console.WriteLine(jo["foo"].ToString());
To prevent the unwanted conversion in PowerShell, wrap your string in a JValue before passing it to JObject.Add(), e.g.:
$jval = New-Object -TypeName Newtonsoft.Json.Linq.JValue -ArgumentList "0745"
$jo.Add("foo", $jval)
Here is the corrected Map-TempStudents function:
function Map-TempStudents {
param($uffStudent) # JObject containing UFF-formatted student
$districtCode = New-Object -TypeName Newtonsoft.Json.Linq.JValue -ArgumentList $uffStudent.Item("DistrictCode").ToString()
Write-Host "districtCode = " $districtCode.ToString()
$tempStudent = New-Object -TypeName Newtonsoft.Json.Linq.JObject
$tempStudent.Add("DistrictNum", $districtCode)
Write-Host "tempStudent(DistrictNum) = " $tempStudent.Item("DistrictNum").ToString()
}

Is it possible to add a validation attribute to a function parameter after the function has already been created?

I found this method of dynamically updating the validate set members of a parameter.
This let's me do something like this:
Function MyFunction([ValidateSet("Placeholder")]$Param1) { "$Param1" }
Update-ValidateSet -Command (Get-Command MyFunction) -ParameterName "Param1" -NewSet #("red","green")
But is there any way of adding a validation attribute that was not already present? Specifically, I have a set of functions that would benefit greatly by having dynamically created validate sets. However, as the link above makes clear, this is a hack, and may break in the future. So I don't want to put a placeholder ValidateSet, in case it needs to be removed in the future. Essentially, I'd like to do something like this:
Function MyFunction($Param1) { "Param1" }
Add-ValidateSet -Command (Get-Command MyFunction) -ParameterName "Param1" -NewSet #("red", "green")
This way, if it ever does break, it would be easier to remove the breaking code. But I have not been able to get this to work. I've tried doing this:
$parameter = (Get-Command MyFunction).Parameters["P1"]
$set = "Red","Orange","Yellow","Green","Blue","Indigo","Violet"
$Attribute = new-object System.Management.Automation.ValidateSetAttribute $Set
$ValidValuesField = [System.Management.Automation.ValidateSetAttribute].GetField("validValues", [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Instance)
$ValidValuesField.SetValue($Attribute, [string[]]$Set)
$parameter.Attributes.Add($Attribute)
But it does not work.
(Get-Command MyFunction).Parameters["P1"].Attributes
shows that the ValidateSet has been added, but tab completion does not work. Comparing it with the results of using the Update-ValidateSet function, it appears that the difference is that the attribute should also appear under
(Get-Command MyFunction).ParameterSets[0].Parameters[0].Attributes
However, that is a ReadOnlyCollection, so I don't seem to be able to add it there. Am I just barking up the wrong tree here? Is this not possible to do?
You accomplish this using dynamic parameters. The dynamic parameters will be evaluated as your command is typed into the command window.
This is from about_Functions_Advanced_Parameters
function Get-Sample {
[CmdletBinding()]
Param ([String]$Name, [String]$Path)
DynamicParam
{
if ($path -match ".*HKLM.*:")
{
$attributes = new-object System.Management.Automation.ParameterAttribute
$attributes.ParameterSetName = "__AllParameterSets"
$attributes.Mandatory = $false
$attributeCollection = new-object `
-Type System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection.Add($attributes)
$dynParam1 = new-object `
-Type System.Management.Automation.RuntimeDefinedParameter("dp1", [Int32], $attributeCollection)
$paramDictionary = new-object `
-Type System.Management.Automation.RuntimeDefinedParameterDictionary
$paramDictionary.Add("dp1", $dynParam1)
return $paramDictionary
}
}

PowerShell Multiple DynamicParam

I'm attempting to create a function for a script module that verifies the previous parameter has been set before it shows the next parameter.
The parameters I need are Identity, Share and Quota. I always want Identity to show, I don't want Share to show until the Identity has been set and I don't want Quota to show until the Share has been set.
I'm able to easily access $Identity, I'm not able to access $Share from within DynamicParam{}. I stepped through the script using PowerGUI and I was only able to see $Share when I hit Begin{}.
I have a way to workaround this by just showing Share/Quota if Identity is set, but ultimately I would like to learn how to keep adding additional parameters based on the previously set parameter.
A copy of the function is below. personfileutility.exe is just an executable we use to interact with the various systems to provision and gather information on a user.
function New-PersonalFiles
{
[CmdletBinding()]
Param
(
# Param1 help description
[Parameter(Mandatory=$true)]
$Identity
)
DynamicParam
{
$paramDictionary = new-object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
if (($Identity -notlike "") -or ($Identity -notlike $null)){
$attributes = new-object System.Management.Automation.ParameterAttribute
$attributes.ParameterSetName = "__AllParameterSets"
$attributes.Mandatory = $true
$lookup = [xml](\\servername\personalfiles\personfileutility.exe -a $Identity)
$type = $lookup.User.user_directory.type.type
if ($type -like "other" -or $type -like "staff") {
$arguments = #()
$arguments += "\\fileserver\sharename"
}
elseif ($type -like "faculty") {
$arguments = #()
$arguments += "\\fileserver\sharename"
$arguments += "\\fileserver\sharename2"
}
elseif ($type -like "student") {
$arguments = #()
$arguments += "\\fileserver2\sharename"
}
$ParamOptions = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $arguments
$attributeCollection = new-object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection.Add($attributes)
$attributeCollection.Add($ParamOptions)
$dynParam1 = new-object -Type System.Management.Automation.RuntimeDefinedParameter("Share", [String], $attributeCollection)
$paramDictionary.Add("Share", $dynParam1)
}
if (($Share -like "\\fileserver\*"))
{
$attributes2 = new-object System.Management.Automation.ParameterAttribute
$attributes2.ParameterSetName = "__AllParameterSets"
$attributes2.Mandatory = $true
$lookup = [xml](\\servername\personalfiles\personfileutility.exe -a $Identity)
$type = $lookup.User.user_directory.type.type
if ($type -like "other" -or $type -like "staff") {
$arguments = #()
$arguments += "15GB"
$arguments += "20GB"
}
elseif ($type -like "faculty") {
$arguments = #()
$arguments += "10GB"
$arguments += "15GB"
}
elseif ($type -like "student") {
$arguments = #()
$arguments += "5GB"
$arguments += "10GB"
}
$ParamOptions2 = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $arguments2
$attributeCollection2 = new-object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection2.Add($attributes2)
$attributeCollection2.Add($ParamOptions2)
$dynParam2 = new-object -Type System.Management.Automation.RuntimeDefinedParameter("Quota", [String], $attributeCollection2)
$paramDictionary.Add("Quota", $dynParam2)
}
return $paramDictionary
}
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
#>
Begin
{
}
Process
{
\\servername\personalfiles\personfileutility.exe -a $Identity -c -q ((Invoke-Expression $PSBoundParameters.Quota) / 1KB) -s $Share
}
End
{
}
}
I'm trying to replicate the "workaround" you're using, but I'm only seeing that I have access to the dynamic parameter Share (not Quota). In order to reproduce your setup, since I don't have access to personfileutility.exe I have commented out two lines and added a third where I hardcode $type to "faculty". For example, in two places I have updated the code to be:
#$lookup = [xml](\\servername\personalfiles\personfileutility.exe -a $Identity)
#$type = $lookup.User.user_directory.type.type
$type = "faculty"
With these changes in place, I can access the Share parameter after I specify an Identity. However, I cannot access the Quota. Do you expect Quota to be available?
If I'm understanding the problem you're asking about, you do not want Share to be accessible until Identity is accessible (which you currently have working). But you in addition, you don't want Quota to be accessible until both Identity and Share are filled out and you don't know how to make that work. Is that correct?
If I'm understanding the problem correctly, I don't believe that PowerShell offers a mechanism to achieve that with Commandlet binding. I think you could make that work by either using a GUI application, or interactively prompting the user for inputs.