Code Review and Help Needed with parameter aliasing

This topic contains 0 replies, has 1 voice, and was last updated by Profile photo of Forums Archives Forums Archives 5 years, 6 months ago.

  • Author
    Posts
  • #6080

    by Schlauge at 2013-04-24 09:30:07

    Need help understanding parameter aliasing and why something is not working. I created this Get-UpTime script a long time ago and have been using it pretty effectively since then. But now with the birth of Powershell.org's Forum Code Review, I started tinkering wiht this script again to work out the kinks.

    I can pass a bunch of hostnames [string[]] to the function and it gives me what I want.... I've added aliases so the script works with different piped in property names. I've not had issue with a number of Cmdlets that use hostnames, (i.e. Citrix's XenApp Cmdlets work, Get-XAServer uses the ServerName for it's property name) but Get-ADComputer does not work. It seems to always default to the distinguished name, even if I create an alias for the property name I want.

    Here is my code:
    [code2=powershell]< #
    .Synopsis
    This is gets the up-time of the computer.
    .Description
    This will query the computer and report the amount of time since last boot.

    .Parameter ComputerName
    Name of computer

    .Example
    Get-UpTime

    .Example
    Get-UpTime DC01

    .Notes
    NAME: Get-UpTime
    AUTHOR: Phil Bossman
    LASTEDIT: 12/01/2011

    #>
    [CmdletBinding()]
    Param (
    [parameter(
    Mandatory=$True,
    ValueFromPipeline=$true,
    ValueFromPipelineByPropertyName=$true,
    HelpMessage="Missing ComputerName."
    )]
    [alias("DNSHostname","MachineName","ServerName","Name")]
    $ComputerName

    )

    BEGIN {
    $obj = New-Object PSObject
    Write-Verbose $ComputerName.Count
    Write-Verbose "In Begin – $ComputerName"
    }

    PROCESS {
    Write-Verbose "In Process – $ComputerName"
    Foreach ( $computer in $ComputerName ) {
    if (test-connection -computername $ComputerName -Quiet -Count 1 -ErrorAction SilentlyContinue)
    {
    $pc = Get-WmiObject Win32_OperatingSystem -ComputerName $ComputerName
    $BootTime = $pc.ConvertToDateTime($pc.LastBootUpTime)
    $now = Get-Date
    $UpTime = $now – $BootTime
    $obj = $obj | Select ComputerName, Days, Hours, Minutes
    $obj.Days = $Uptime.Days
    $obj.Hours = $Uptime.Hours
    $obj.Minutes = $uptime.Minutes
    $obj.ComputerName = $ComputerName
    $obj
    } else {
    Write-Host "Host Unavailable: " $ComputerName
    }
    }
    }

    END {
    $obj = $null
    }
    }[/code2]

    Examples:
    [code2=powershell]PS C:\> "TTIT070" | Get-UpTime

    ComputerName Days Hours Minutes
    ———— —- —– ——-
    TTIT070 0 2 41[/code2]
    No Issue here when I pass in a [String]

    [code2=powershell]PS C:\> Get-XAServer | Get-UpTime -Verbose
    VERBOSE: 0
    VERBOSE: In Begin –
    VERBOSE: In Process – CTXZDC01

    ComputerName Days Hours Minutes
    ———— —- —– ——-
    CTXZDC01 1 2 19
    VERBOSE: In Process – CTXZDC02
    CTXZDC02 1 2 25
    VERBOSE: In Process – CTXXML01
    CTXXML01 1 2 8
    VERBOSE: In Process – CTXXML02
    CTXXML02 1 2 11
    VERBOSE: In Process – CTXD101
    CTXD101 2 8 57
    VERBOSE: In Process – CTXD102[/code2]

    Still Happy....Get-XAServer is matching on the ServerName field
    ... But .....

    [code2=powershell]PS C:\> Get-ADComputer TTIT070 | Get-UpTime -Verbose
    VERBOSE: 0
    VERBOSE: In Begin –
    VERBOSE: In Process – CN=TTIT070,OU=Win7 Workstations,DC=mhs,DC=net
    Host Unavailable: CN=TTIT070,OU=Win7 Workstations,DC=mhs,DC=net[/code2]

    Get-ADComputer does not match the DNSHostname or Name fields
    I'm at a loss..... So to correct this issue, I can to pull out the property I want...Then pipe it to Get-UpTime and it works

    [code2=powershell]PS C:\> Get-ADComputer TTIT070 | Select -ExpandProperty DNSHostname | Get-UpTime

    ComputerName Days Hours Minutes
    ———— —- —– ——-
    TTIT070.mhs.net 0 2 48[/code2]

    by DonJ at 2013-04-25 07:16:47

    Aliases don't work for picking up pipeline input that way. If you have a command that produces an object having a "DNSHostName" property, and you need that connecting to your -ComputerName parameter, you would use Select-Object to do the mapping.


    Get-Whatever | Select *,@{n='computername';e={$_.DNSHostNameOrWhatever}} | Do-Something

    Also remember that ByValue parameter input always runs first, so that has to FAIL in order for ByPropertyName to kick in.

    by poshoholic at 2013-04-25 07:46:01

    Actually, I think aliases do work for picking up pipeline input, but only if the parameter name itself doesn't match. I've used this myself, and native PowerShell commands use aliases to get the appropriate property matching from the pipeline.

    Get-ADComputer returns objects that contain both Name and DNSHostName properties, both of which are in the alias list. Do those objects also have a ComputerName property (visible or hidden)? I'm not in front of Active Directory at the moment, so I can't test. I think you should invoke this command:
    Get-ADComputer | Format-Table -Force -Property ComputerName,DNSHostname,MachineName,ServerName,Name
    Could you run that and see which properties are possible matches in this case? Without running it myself, your output makes me think there must be a ComputerName property on those objects that is getting in the way.

    by Schlauge at 2013-04-27 18:07:17

    Don,
    I understand that if a cmdlet/function took ComputerName, you can build the piped object. But if you were building this cmdlet/function and wanted to add the robustness of allowing for multiple ways to identify the property, how would accomplish this without aliasing?

    Kirk,
    Here is the output [code2=powershell]PS C:\> get-adcomputer TTIT070 | FT -Force -Property ComputerName, MachineName, DNSHostname, Name, ServerName

    ComputerName MachineName DNSHostname Name ServerName
    ———— ———– ———– —- ———-
    {} TTIT070.domain.local TTIT070[/code2]

    What's making my head hurt is that I would gladly accept any of these values, but for some reason I keep getting a different property.
    You've got me thinking of doing some tracing, but I've not had much success....Any ideas of a good Trace or Debug HOWTO so I might determine exactly how the DistinguishedName Field gets assigned instead of Name or DNSHostname

    by Schlauge at 2013-04-27 18:28:42

    Fo those who don't have direct access to the tools, I've posted the Trace-Command results. I'm not clear on why it's returning, what I assume is generic, "DistinguishedName" value when I'm expressly adding an alias for a property that it does have.
    [code2=powershell]PS C:\> Trace-Command -Name ParameterBinding -PSHost -Expression {Get-ADComputer TSTL029 | Get-UpTime }
    DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Get-ADComputer]
    DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Get-ADComputer]
    DEBUG: ParameterBinding Information: 0 : BIND arg [TSTL029] to parameter [Identity]
    DEBUG: ParameterBinding Information: 0 : BIND arg [TSTL029] to param [Identity] SKIPPED
    DEBUG: ParameterBinding Information: 0 : BIND arg [TSTL029] to parameter [Identity]
    DEBUG: ParameterBinding Information: 0 : COERCE arg to [Microsoft.ActiveDirectory.Management.ADComputer]
    DEBUG: ParameterBinding Information: 0 : Trying to convert argument value from System.String to Microsoft.ActiveDirectory.Management.ADComputer
    DEBUG: ParameterBinding Information: 0 : CONVERT arg type to param type using LanguagePrimitives.ConvertTo
    DEBUG: ParameterBinding Information: 0 : CONVERT SUCCESSFUL using LanguagePrimitives.ConvertTo: [TSTL029]
    DEBUG: ParameterBinding Information: 0 : Executing VALIDATION metadata: [System.Management.Automation.ValidateNotNullAttribute]
    DEBUG: ParameterBinding Information: 0 : BIND arg [TSTL029] to param [Identity] SUCCESSFUL
    DEBUG: ParameterBinding Information: 0 : BIND cmd line args to DYNAMIC parameters.
    DEBUG: ParameterBinding Information: 0 : DYNAMIC parameter object: [Microsoft.ActiveDirectory.Management.Commands.GetADComputerParameterSet]
    DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Get-ADComputer]
    DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Get-UpTime]
    DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Get-UpTime]
    DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Get-UpTime]
    DEBUG: ParameterBinding Information: 0 : BIND arg [] to parameter [ComputerName]
    DEBUG: ParameterBinding Information: 0 : BIND arg [] to param [ComputerName] SUCCESSFUL
    DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
    DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
    DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [New-Object]
    DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [New-Object]
    DEBUG: ParameterBinding Information: 0 : BIND arg [PSObject] to parameter [TypeName]
    DEBUG: ParameterBinding Information: 0 : BIND arg [PSObject] to param [TypeName] SUCCESSFUL
    DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [New-Object]
    DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
    DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing
    DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Write-Verbose]
    DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Write-Verbose]
    DEBUG: ParameterBinding Information: 0 : BIND arg [0] to parameter [Message]
    DEBUG: ParameterBinding Information: 0 : BIND arg [0] to param [Message] SKIPPED
    DEBUG: ParameterBinding Information: 0 : BIND arg [0] to parameter [Message]
    DEBUG: ParameterBinding Information: 0 : COERCE arg to [System.String]
    DEBUG: ParameterBinding Information: 0 : Trying to convert argument value from System.Int32 to System.String
    DEBUG: ParameterBinding Information: 0 : CONVERT arg type to param type using LanguagePrimitives.ConvertTo
    DEBUG: ParameterBinding Information: 0 : CONVERT SUCCESSFUL using LanguagePrimitives.ConvertTo: [0]
    DEBUG: ParameterBinding Information: 0 : BIND arg [0] to param [Message] SUCCESSFUL
    DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Write-Verbose]
    DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
    DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing
    DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Write-Verbose]
    DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Write-Verbose]
    DEBUG: ParameterBinding Information: 0 : BIND arg [In Begin – ] to parameter [Message]
    DEBUG: ParameterBinding Information: 0 : BIND arg [In Begin – ] to param [Message] SUCCESSFUL
    DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Write-Verbose]
    DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
    DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing
    DEBUG: ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Get-UpTime]
    DEBUG: ParameterBinding Information: 0 : PIPELINE object TYPE = [Microsoft.ActiveDirectory.Management.ADComputer]
    DEBUG: ParameterBinding Information: 0 : RESTORING pipeline parameter's original values
    DEBUG: ParameterBinding Information: 0 : Parameter [ComputerName] PIPELINE INPUT ValueFromPipeline NO COERCION
    DEBUG: ParameterBinding Information: 0 : BIND arg [CN=TSTL029,OU=Images,OU=IT,DC=domain,DC=local] to parameter [ComputerName]
    DEBUG: ParameterBinding Information: 0 : BIND arg [CN=TSTL029,OU=Images,OU=IT,DC=domain,DC=local] to param [ComputerName] SUCCESSFUL
    DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Get-UpTime]
    DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Write-Verbose]
    DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Write-Verbose]
    DEBUG: ParameterBinding Information: 0 : BIND arg [In Process – CN=TSTL029,OU=Images,OU=IT,DC=domain,DC=local] to parameter [Message]
    DEBUG: ParameterBinding Information: 0 : BIND arg [In Process – CN=TSTL029,OU=Images,OU=IT,DC=domain,DC=local] to param [Message] SUCCESSFUL
    DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Write-Verbose]
    DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
    DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing
    DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Test-Connection]
    DEBUG: ParameterBinding Information: 0 : BIND arg [CN=TSTL029,OU=Images,OU=IT,DC=domain,DC=local] to parameter [ComputerName]
    DEBUG: ParameterBinding Information: 0 : COERCE arg to [System.String[]]
    DEBUG: ParameterBinding Information: 0 : Trying to convert argument value from System.Management.Automation.PSObject to System.String[]
    DEBUG: ParameterBinding Information: 0 : ENCODING arg into collection
    DEBUG: ParameterBinding Information: 0 : Binding collection parameter ComputerName: argument type [PSObject], parameter type [System.String[]], collection type
    Array, element type [System.String], coerceElementType
    DEBUG: ParameterBinding Information: 0 : Creating array with element type [System.String] and 1 elements
    DEBUG: ParameterBinding Information: 0 : Argument type PSObject is not IList, treating this as scalar
    DEBUG: ParameterBinding Information: 0 : COERCE arg to [System.String]
    DEBUG: ParameterBinding Information: 0 : Trying to convert argument value from System.Management.Automation.PSObject to System.String
    DEBUG: ParameterBinding Information: 0 : CONVERT arg type to param type using LanguagePrimitives.ConvertTo
    DEBUG: ParameterBinding Information: 0 : CONVERT SUCCESSFUL using LanguagePrimitives.ConvertTo: [CN=TSTL029,OU=Images,OU=IT,DC=domain,DC=local]
    DEBUG: ParameterBinding Information: 0 : Adding scalar element of type String to array position 0
    DEBUG: ParameterBinding Information: 0 : Executing VALIDATION metadata: [System.Management.Automation.ValidateNotNullOrEmptyAttribute]
    DEBUG: ParameterBinding Information: 0 : BIND arg [System.String[]] to param [ComputerName] SUCCESSFUL
    DEBUG: ParameterBinding Information: 0 : BIND arg [True] to parameter [Quiet]
    DEBUG: ParameterBinding Information: 0 : COERCE arg to [System.Management.Automation.SwitchParameter]
    DEBUG: ParameterBinding Information: 0 : Parameter and arg types the same, no coercion is needed.
    DEBUG: ParameterBinding Information: 0 : BIND arg [True] to param [Quiet] SUCCESSFUL
    DEBUG: ParameterBinding Information: 0 : BIND arg [1] to parameter [Count]
    DEBUG: ParameterBinding Information: 0 : COERCE arg to [System.Int32]
    DEBUG: ParameterBinding Information: 0 : Parameter and arg types the same, no coercion is needed.
    DEBUG: ParameterBinding Information: 0 : Executing VALIDATION metadata: [System.Management.Automation.ValidateRangeAttribute]
    DEBUG: ParameterBinding Information: 0 : BIND arg [1] to param [Count] SUCCESSFUL
    DEBUG: ParameterBinding Information: 0 : BIND arg [SilentlyContinue] to parameter [ErrorAction]
    DEBUG: ParameterBinding Information: 0 : COERCE arg to [System.Management.Automation.ActionPreference]
    DEBUG: ParameterBinding Information: 0 : Trying to convert argument value from System.String to System.Management.Automation.ActionPreference
    DEBUG: ParameterBinding Information: 0 : CONVERT arg type to param type using LanguagePrimitives.ConvertTo
    DEBUG: ParameterBinding Information: 0 : CONVERT SUCCESSFUL using LanguagePrimitives.ConvertTo: [SilentlyContinue]
    DEBUG: ParameterBinding Information: 0 : BIND arg [SilentlyContinue] to param [ErrorAction] SUCCESSFUL
    DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Test-Connection]
    DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Test-Connection]
    DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
    DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing
    DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Write-Host]
    DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Write-Host]
    DEBUG: ParameterBinding Information: 0 : BIND REMAININGARGUMENTS cmd line args to param: [Object]
    DEBUG: ParameterBinding Information: 0 : BIND arg [System.Collections.ArrayList] to parameter [Object]
    DEBUG: ParameterBinding Information: 0 : COERCE arg to [System.Object]
    DEBUG: ParameterBinding Information: 0 : Parameter and arg types the same, no coercion is needed.
    DEBUG: ParameterBinding Information: 0 : BIND arg [System.Collections.ArrayList] to param [Object] SUCCESSFUL
    DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Write-Host]
    DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
    Host Unavailable: CN=TSTL029,OU=Images,OU=IT,DC=domain,DC=local
    DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing
    DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing
    DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing

    PS C]

    by mjolinor at 2013-04-27 18:57:53

    Looking at that trace output, it seems to be struggling with that object type.

    Just out of curiousity, does it behave differently if you pipe the get-adcomputer output to select * before you feed it to the function?

    by DonJ at 2013-04-27 19:03:03

    It can. Look at the output in GM – you get a different object after selecting it.

    by poshoholic at 2013-04-27 21:44:37

    I think I just put my finger on the issue. You haven't specified a type for your $ComputerName parameter, yet you want it to be a string.

    Here's what I believe happens:

    Your $ComputerName parameter does not have a type; therefore, it is of type [System.Object]. When PowerShell tries to bind parameter values from pipeline input, it first looks at the incoming object and checks to see if there is a parameter that accepts pipeline input by value with a compatible object type. If so, it binds that parameter. If not, it then checks to see if it can bind parameter values by property name according to the current parameter configuration, first checking the name of the parameter against the property names and then checking the parameter alias against the property name.

    In this case, $ComputerName gets assigned the actual object returned from Get-ADComputer because [System.Object] is compatible with any objects coming from a pipeline. Then inside the body of the command, the command invokes Test-Connection, passing the ComputerName parameter of the command (an AD computer object) into the ComputerName parameter of the Test-Connection cmdlet (which is of type String[]). I believe the string representation of an AD Computer object returned from Microsoft's Get-ADComputer command is the distinguished name of that computer, so the DN gets passed into Test-Connection.

    Assuming this is an accurate assessment of what is going on here, you simply need to define $ComputerName as type [string[]] and you should be good to go.

    Also, you can verify my theory here by checking the type of the $ComputerName you receive by adding this to your command's process block:
    Write-Verbose $ComputerName.GetType().FullName
    Try adding that Write-Verbose call first, see if that confirms my theory, and then try applying the [string[]] type to the $ComputerName parameter.

    by Schlauge at 2013-05-06 18:01:25

    Thanks Kirk for sending me down the path, where I finally found an acceptable solution.

    I discovered that for some reason, the [String] representation of the ADComputer object is the distinguished name, and because I'm trying to accept it as a piped value, it will always give me the string value even if I try to grab one of it's properties.

    Added before line 44 in the original script.
    [code2=powershell]If ($computer.GetType().Name -eq 'ADComputer') {
    $computer = $computer.DNSHostname
    }[/code2]
    Though I don't like validating the input in the body of the script, this solution gets me my flexibility. I can now pass in a String, an object that has the properties I can alias (Citrix XenApp Cmdlets), and lastly an Coomputer object from the ActiveDirectory Module

You must be logged in to reply to this topic.