Script - PARAM - prompt for credentials?

This topic contains 7 replies, has 2 voices, and was last updated by Profile photo of Dave Wyatt Dave Wyatt 3 years, 4 months ago.

  • Author
    Posts
  • #12018
    Profile photo of Jake Sully
    Jake Sully
    Participant

    Hello,

    I have a script I am working on which gathers information from remote systems. Sometimes I can use my local credentials so I specified what is in the parameters below, but then other times I need to get prompted for credentials or perhaps store them encrypted based on the "environment" the systems are in such as a lab, separate domain, etc.

    Can someone help me get this to work either by getting prompted or by storing them somewhere, both examples would be great. I've tried specifying a variable and I'm stuck because I can't get it to work and I'm sure it's because I'm putting the variable in the wrong place, but this is my 1st attempt at using parameters with cmdlet binding, it seemed easier but now I'm not sure 🙂

    [CmdLetBinding()]
    Param(
    [Parameter(ValueFromPipeline=$true)]
    [string]$ComputerName=$env:ComputerName,
    [System.Management.Automation.PSCredential]$credential
    )

    PROCESS
    {
    $computerProps = @{}
    If (($ComputerName -eq $env:ComputerName) -or (-not $credential))
    {
    If (-not $credential) {Write-Warning "Using default creds, connections might fail!"}
    try
    {
    $computerProps = @{
    ComputerName=$ComputerName;
    OS=(Get-WMIObject -Class Win32_OperatingSystem -ComputerName $ComputerName).Caption;
    ServicePack=(Get-WMIObject -Class Win32_OperatingSystem -ComputerName $ComputerName).CSDVersion
    }
    }
    catch
    {
    $computerProps = @{
    ComputerName="$ComputerName – Denied";
    OS="";
    ServicePack=""
    }
    }
    }
    else
    {
    If (test-connection $ComputerName -quiet -count 1)
    {
    $computerProps = @{
    ComputerName=$ComputerName;
    OS=(Get-WMIObject -Class Win32_OperatingSystem -ComputerName $ComputerName -Credential $Credential).Caption;
    ServicePack=(Get-WMIObject -Class Win32_OperatingSystem -ComputerName $ComputerName -Credential $Credential).CSDVersion
    }
    }
    else
    {
    $computerProps = @{
    ComputerName="$ComputerName – SystemDown";
    OS="";
    ServicePack=""
    }
    }
    }
    $outputObject = new-object PSObject -property $computerProps
    Write-Output $outputObject
    }

  • #12068
    Profile photo of Jake Sully
    Jake Sully
    Participant

    Dave – Thanks a lot! That makes sense now that you detailed it. I want to make sure I'm clear.

    With just this param right below, I'm stating to use the credentials I'm logged into the system with by not actually entering anything. Then in your example below my parameter then I can run the script but prior to running it then I set the credential variable, so that is done outside of the script? I also noticed you added the function portion, is it better to run this as a function as opposed to a script? I'm gathering information from remote computers. I've run into issues trying to run functions inside other scripts or even just running a function alone. I use .\function-name and it fails, then I've tried putting in the full path, etc.

    {
    [CmdletBinding()]
    Param(
    [Parameter(ValueFromPipeline=$true)]
    [string]$ComputerName = $env:ComputerName,

    [System.Management.Automation.PSCredential]$Credential
    )

  • #12072
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    The way I wrote the sample code, yes, if you don't specify the Credential parameter, it will just authenticate as the current user.

    As to whether you want this to be a function or a script file, that's up to you. I tend to use functions either added to a module or placed into my profile wherever I can, and write a script file as the entry point when the PowerShell code needs to be launched from another process (such as from a scheduled task.) From inside a PowerShell session, though, there's very little difference between running a function and running a script. They're both converted to ScriptBlock objects, can accept and produce objects on the pipeline, can be dot-sourced, etc.

  • #12922
    Profile photo of Jake Sully
    Jake Sully
    Participant

    Hi Dave,
    I started back on this and I'm stuck. So I added this to my function: [System.Management.Automation.PSCredential]$Credential

    So now the function start looks like this, but I'm lost on where to add in the rest to get the function to use credentials I want to get prompted for. I also don't know if I "need" to do that or if I can just do it when I run the script.

    In the "ideal solution", it would be great to get prompted if I got access denied when the function hits a server that's not on the domain, so I could see the server name and enter the right creds.

    But just being able to specify creds is fine.

    [CmdletBinding()]

    PARAM(
    [Parameter(ValueFromPipeline=$true)]
    [String[]]$ComputerName = $Computers,
    [System.Management.Automation.PSCredential]$Credential
    [String]$Errors = ".\Errors.log"
    )

    BEGIN {}#PROCESS BEGIN

    PROCESS{
    FOREACH ($Computer in $ComputerName) {

  • #12923
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    I gave an example of that in the Process block of the code in my original reply. The only difference between that and what you're doing now is the addition of the foreach ($computer in $ComputerName) loop (because you've changed $ComputerName from [string] to [string[]] ; a good change.)

        process
        {
            foreach ($computer in $ComputerName)
            {
                $params = @{
                    ComputerName = $computer
                    Class = 'Win32_OperatingSystem'
                }
    
                if ($Credential)
                {
                    $params['Credential'] = $Credential
                }
    
                Get-WmiObject @params
            }
        }
    
    

    I prefer to use splatting for this type of conditional parameter passing, as shown in the examples so far. The alternative would be to have a conditional and two different calls to Get-WmiObject:

        process
        {
            foreach ($computer in $ComputerName)
            {
                if ($Credential)
                {
                    Get-WmiObject -ComputerName $computer -Class 'Win32_OperatingSystem' -Credential $Credential
                }
                else
                {
                    Get-WmiObject -ComputerName $computer -Class 'Win32_OperatingSystem'
                }
            }
        }
    
  • #12929
    Profile photo of Jake Sully
    Jake Sully
    Participant

    Thanks again Dave! Really appreciate it, I'm trying 🙂

    I'm not sure how to do the splatting, I've read about it and seen examples, but I'm not clear on how to add that in.

    If I used your original example then the part I'm confused on is where I put the $Credential? I might be over thinking this, but do I simply just add the code you provided which I've copied below to the function so that anytime the function does a Get-WmiObject, I add in the if/else statement?

    process
    {
    foreach ($computer in $ComputerName)
    {
    if ($Credential)
    {
    Get-WmiObject -ComputerName $computer -Class 'Win32_OperatingSystem' -Credential $Credential
    }
    else
    {
    Get-WmiObject -ComputerName $computer -Class 'Win32_OperatingSystem'
    }
    }
    }

  • #12931
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    Splatting is when you set up a hashtable that maps parameter names to values. You can even set up the hashtable so it's either empty or contains the Credential, instead of all of the parameters:

    # Start with an empty hashtable
    $hashTable = @{}
    
    # If a Credential was passed in, add it to the hashtable
    if ($Credential)
    {
        $hashTable['Credential'] = $Credential
    }
    
    # When calling a command, splat the hashtable using the @ operator instead of the $ operator:
    Get-WmiObject -ComputerName $computer -Class Win32_OperatingSystem @hashTable
    

    It's up to you, but I much prefer to use splatting instead of that if / else approach with two separate calls to Get-WmiObject.

    You can read more about this topic in the about_Splatting help file. Technically, you can splat arrays as well (which corresponds to positional parameters instead of named), but it's a better idea to use hashtables and named parameters wherever possible.

  • #12019
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    You're already on the right track with your param block declaring an optional parameter of type PSCredential. Now you just need to code around that properly inside the script / function. (This has nothing to do with how you got the credential that is being passed to this code; more on that afterward.)

    function Test-SomeFunction
    {
        [CmdletBinding()]
        Param(
            [Parameter(ValueFromPipeline=$true)]
            [string]$ComputerName = $env:ComputerName,
        
            [System.Management.Automation.PSCredential]$Credential
        )
    
        process
        {
            # Example of using $Credential only if it was passed in.
    
            $params = @{
                ComputerName = $ComputerName
                Class = 'Win32_OperatingSystem'
            }
    
            if ($Credential)
            {
                $params['Credential'] = $Credential
            }
    
            Get-WmiObject @params
        }
    }
    

    To prompt the user to enter credentials, use the Get-Credential cmdlet:

    $cred = Get-Credential
    Test-SomeFunction -ComputerName SomeRemoteServer -Credential $cred
    

    To save the credentials to disk, you have to decide on a few things. Do you want anyone else to be able to read the saved credentials, or are they only for you (on the same computer where you saved them)? If it's the latter, PowerShell gives you an easy way to save the credentials, where the password will be encrypted by the Data Protection API:

    # Generating the saved credentials
    $cred = Get-Credential
    $cred | Export-CliXml -Path $home\MySavedCredentials.xml
    
    # Using them later
    $myCreds = Import-CliXml -Path $home\MySavedCredentials.xml
    Test-SomeFunction -ComputerName SomeRemoteServer -Credential $myCreds
    

    If you need to be able to read the password when the script is running as some other account, or on another computer, then you're going to have to take a more active role in the security of the password. That's harder to get right, but we can go deeper into that topic if needed.

You must be logged in to reply to this topic.