Author Posts

December 17, 2013 at 10:34 pm

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
}

December 18, 2013 at 5:09 am

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.

December 20, 2013 at 1:31 pm

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
)

December 20, 2013 at 1:47 pm

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.

February 3, 2014 at 6:51 pm

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) {

February 3, 2014 at 7:44 pm

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'
            }
        }
    }

February 4, 2014 at 6:31 am

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'
}
}
}

February 4, 2014 at 6:40 am

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.