Sometimes I like to go back and see where I can improve my scripts. A few weeks ago I showed you the Unifi Controller script. Where it pulls the client information from each site. Well, I wanted to improve upon this script. A problem I see with this script is the plain text password. The unifi controller I have to test only accepts plain text, but I don’t want to have a password inside my PowerShell history. Today I want to show you how to convert Get-Credentials to Plain Text.

Get-Credentials as a Parameter

We first need to remove the Username and Password from the parameters and then create a secure credential parameter. By default, we want this to be a mandatory parameter. The type of parameter we will be using is a System Management Automation PScredential. This is what the parameter will look like.

[parameter( 
            HelpMessage = "Unifi Username and Password", 
            Mandatory = $true)][alias('UserNamePassword')][System.Management.Automation.PSCredential]$Credential

Convert Get-Credentials to Plain Text

Now it’s time to Covert the PSCredentials back to plain text. Inside our PSCredentials lives the username and password we gave the system. Thus we pull the secure string password from the PSCredentials.

$SecurePassword = $Credential.Password

Next, we are going to use the Marshal Class. This class is designed to bridge the gap between managed and unmanaged programming models. The method we will be using is the Secure String to BSTR. This method Allocates an unmanaged binary string and copies the content of a managed secure string object into it. Basically, we find where it lives in memory and combine it. For more information.

 $CodedPassword = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword)

Aftward, we Take this coded data and push it into the following method. The PtrToStringAuto. This method allocates a managed string and copies all the characters up to the first null. This copy is from the string stored in unmanaged memory. This is why we need the Coded Password to tell us where the password lives in memory. Basically, I’m taking the coded password and making it human-readable.

$Password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($CodedPassword)

The Script Rebuilt

Now we have a way to decrpt the get credential command, it’s time to dive back into the script. Here is the rebuilt script.

function Get-UnifiSitesClients {
        param (
            [parameter(
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,    
                HelpMessage = "Unifi Username and Password", 
                Mandatory = $true)][alias('IP')][ipaddress]$IPaddress,
            [parameter( 
                HelpMessage = "Unifi Username and Password", 
                Mandatory = $true)][alias('UserNamePassword')][System.Management.Automation.PSCredential]$Credential,
            [parameter(
                HelpMessage = "Port Number", 
                Mandatory = $true)][alias('Port')][init]$portNumber
        )
        $SecurePassword = $Credential.Password
        $CodedPassword = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword)
        $Password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($CodedPassword)
        $Username = $Credential.UserName

        $uri = "https://$($IPaddress):$portNumber/api/login"
        $headers = @{'Accept' = 'application/json' }
        $params = @{
            'username' = $username;
            'password' = $Password;
        }
        $body = $params | ConvertTo-Json
     
        $response = Invoke-RestMethod -Uri $uri `
            -Body $body `
            -Method Post `
            -ContentType 'application/json' `
            -Headers $headers `
            -SkipCertificateCheck `
            -SessionVariable s
        $uri = "https://$($IPaddress):$portNumber/api/self/sites"
        $sites = Invoke-RestMethod -Uri $uri `
            -Method Get `
            -ContentType 'application/json' `
            -Headers $headers `
            -SkipCertificateCheck `
            -Websession $s
        $Return = Foreach ($Site in $sites.data) {
            $Uri = "https://$($IPaddress):$portNumber/api/s/$($Site.name)/stat/sta"
            $Clients = (Invoke-RestMethod -Uri $Uri `
                -Method Get `
                -ContentType 'application/json' `
                -Headers $headers `
                -SkipCertificateCheck `
                -Websession $s).data
            Foreach ($Client in $Clients) {
                [pscustomobject][ordered]@{
                    Site = $Site.name
                    SiteDescritption = $Site.desc
                    OUI = $client.OUI
                    MacAddress = $client.mac 
                    IPAddress = $Client.IP
                    SwitchMac = $client.sw_mac
                    SwitchPort = $client.sw_port
                    WireRate = $client.wired_rate_mbps
                }
            }
        }
        $return
    }

Image created with mid-journey