简体   繁体   中英

Passing a PowerShell variable as a cmdlet parameter

I am learning PowerShell to write tools for my team. I was in a pinch today and got this working, but I want to streamline it by removing the IF statements in the ForEach loop, since the only difference between the commands is the parameter -replace or -remove .

Write-Host "This script removes or replaces en masse users' msds-SyncServerURL property"

DO {
    $TargetSSU=Read-Host "`nWhat msds-SyncServerURL do you want to replace or remove?"
    $UsersToBeFixed=Get-ADUser -Filter {msds-SyncServerURL -like $TargetSSU} -Properties ('name','samaccountname','msDS-SyncServerURL')
    IF ($UsersToBeFixed -eq $null) {
        Write-Host "`nNo users appear to have $TargetSSU as their msds-SyncServerURL value. Please try again."
    }
} UNTIL ($UsersToBeFixed -ne $null)

Write-Host "`n`nThe following users have $TargetSSU as their msds-SyncServerURL value:"
$UsersToBeFixed |select name,samaccountname,msds-syncserverurl|ft

DO {
    $Action=Read-Host "Do you want to [R]emove or [U]pdate $TargetSSU?"
} Until (($Action -eq "R") -or ($Action -eq "U"))    
    IF ($Action -eq "U") {
        DO {
            $NewSyncServer=Read-Host "`nEnter the new Sync Server's hostname (not the URL)"
            Write-Host "`nChecking to see if $NewSyncServer has a userfile share..."
            $VerifySyncServer=Test-Path \\$NewSyncServer\userfiles
            IF ($VerifySyncServer -eq $false) {
                Write-host "`n$NewSyncServer does not appear to be a valid Sync Server hostname. Please try again."
            }
        } UNTIL ($VerifySyncServer -eq $true)
        $TargetSSU="https://$NewSyncServer.ourdomain.com"
    }

ForEach ($userToBeFixed in $UsersToBeFixed) {
    Write-Host "`nFixing" ($usertobefixed).name
    IF ($Action -eq "R") {
        Set-ADObject -identity $userToBeFixed -remove @{"msDS-SyncServerUrl" = $TargetSSU}
    }
    IF ($Action -eq "U") {
        Set-ADObject -identity $userToBeFixed -replace @{"msDS-SyncServerUrl" = $TargetSSU}
    }
}

Write-Host "`nHere is the result of the operation:"
foreach ($userToBeFixed in $UsersToBeFixed) {
    Get-ADUser -Identity $userToBeFixed -Properties ('name','samaccountname','msDS-SyncServerURL')|select name,samaccountname,msds-syncserverurl
}

I initially had the following switch in place, trying various permutations of quotation marks and even {$action="replace"}:

Switch($action)
{
    R {'remove'}
    U {'replace'}
}

I also tried Invoke-Expression in the ForEach loop:

$CMD="Set-ADObject -identity $userToBeFixed -$action @{`"msDS-SyncServerUrl`" = $TargetSSU}"
Invoke-Expression -Command $CMD

Invariably, the Set-ADObject cmdlet would fail, typically complaining about Set-ADObject not being able to find positional parameters that accepts arguments like '-remove', 'System.Object[]' or 'System.Collections.Hashtable'.

I isolated the issue to the Set-ADObject not liking the $action variable being used as a parameter. If I replaced -$action with -replace or -remove it would work (as it is in the above code snippet).

I realize this is a minor issue, but it bothers me to have such redundant code for seemingly no reason. I would love to learn how to work around this.

Also, unrelated, I haven't really found a better way to do this:

Until (($Action -eq "R") -or ($Action -eq "U"))

I have searched and tried other solutions like:

Until ($Action -eq @{"R" -or "U"})

But can't seem to consolidate the evaluation of multiple conditions. This bothers me, but not as much as my main question here.

Please go easy on me. I'm new to this whole thing. If anyone sees anything else I can improve let me know. I want to learn this.

Thanks.

You can solve this with splatting .

For example:

$action = 'Recurse'
$params = @{ $action = $true }
Get-ChildItem @params

The example is functionally equivalent to Get-ChildItem -Recurse

Persistent13's helpful answer shows you how to use splatting to pass parameters dynamically, via a hashtable.

As for:

Also, unrelated, I haven't really found a better way to do this:

  Until (($Action -eq "R") -or ($Action -eq "U")) 

PowerShell offers array containment operators: -contains (PSv1+, array on the LHS) and -in (PSv3+, array on the RHS):

# PSv3+
$Action -in 'R', 'U'

# Equivalent, PSv1+
'R', 'U' -contains $Action

Both forms compare the scalar operand to every element of the array operand (using -eq logic) and return $True as soon as the first match (if any) is found.

Another option is to use the -match operator with a regular expression:

$Action -match '^(R|U)$'

I like to move everything that doesn't involve script logic, ie functions, into a different file from the main PowerShell script. Most of my scripts follow this structure:

Edit-SyncServerUrl.ps1

#requires -Version 5.1

<#
    Company header
#>

<#
.SYNOPSIS
Comment-based help
#>
[CmdletBinding(SupportsShouldProcess)]
Param([String]$Option)

. "$PSScriptRoot\scriptfunctions.ps1"

<# .. variables .. #>

Switch -Regex ($Option)
{
    '^rem' {Edit-SyncServerURL -Remove}
    '^rep' {Edit-SyncServerURL -Replace}
    Default {Write-Host "Invalid option passed: $Option"}
}

In your specific example, I would take all the logic of prompting for settings, etc. into a function that picks an execution path based on parameters passed to it. You could do

Param(
    [ValidateScript({$_ -match '^(u|r)'})]
    [String]$Option=(Read-Host -Prompt 'Would you like to (r)emove or (u)pdate a target msds-SyncServerUrl?'
)

Switch -Regex ($Option)
{
    '^r' { <# Logic #> }
    '^u' { <# Logic #> }
}

Persistent13's explanation of how to splat parameters that require hashtables did the trick there.

mklement0's solution to streamlining comparisons helped clean up that bit of code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM