22 Using someone else’s script

Much as we hope you’ll be able to construct your own PowerShell commands and scripts from scratch, we also realize that you’ll rely heavily on the internet for examples. Whether you’re repurposing examples from someone’s blog, or tweaking a script you’ve found in an online script repository, being able to reuse someone else’s PowerShell script is an important core skill. In this chapter, we’ll walk you through the process we use to understand someone else’s script and make it our own.

Thanks Credit goes to Brett Miller, who provided us with the script we use in this chapter. We deliberately asked him for a less-than-perfect script that doesn’t necessarily reflect all of the best practices we normally like to see. And in some instances, we worsened this script to make this chapter better reflect the real world. We truly appreciate his contribution to this learning exercise!

Note that we’ve also selected these scripts specifically because they use advanced PowerShell features that we haven’t taught you. Again, we think that’s realistic: you’re going to run across stuff that looks unfamiliar, and part of this exercise is about how to quickly figure out what the script is doing, even if you aren’t fully trained on every technique the script uses.

22.1 The script

This is a true real-world scenario that most of our students have been through. They have a problem, go the internet, find a script that does what they need to do. It’s important that you understand what is happening. The following listing shows the complete script, which is entitled Get-AdExistence.ps1. This script is designed to work with Microsoft’s AD cmdlets. This will only work on a Windows-based computer. If you do not have access to a Windows machine with Active Directory installed, you can still follow along with us, as we will be going through this script piece by piece.

Listing 22.1 Get-AdExistence.Ps1

<#
.Synopsis
   Checks if computer account exists for computer names provided
.DESCRIPTION
   Checks if computer account exists for computer names provided
.EXAMPLE
   Get-ADExistence $computers
.EXAMPLE
   Get-ADExistence "computer1","computer2"
#>
function Get-ADExistence{
    [CmdletBinding()]
    Param(
        # single or array of machine names
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true,
                   HelpMessage="Enter one or multiple computer names")]
        [String[]]$Computers
     )
    Begin{}
    Process {
        foreach ($computer in $computers) {
            try {
                $comp = get-adcomputer $computer -ErrorAction stop
                $properties = @{computername = $computer
                                Enabled = $comp.enabled
                                InAD = 'Yes'}
            } 
            catch {
                $properties = @{computername = $computer
                                Enabled = 'Fat Chance'
                                InAD = 'No'}
            } 
            finally {
                $obj = New-Object -TypeName psobject -Property $properties
                Write-Output $obj
            }
        } #End foreach
 
    } #End Process
    End{}
} #End Function

22.1.1 Parameter block

First up is a parameter block, which you learned to create in chapter 19:

Param(
        # single or array of machine names
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
        [String[]]$Computers
     )

This parameter block looks a bit different, but it appears to be defining a -Computers parameter that can accept an array, and it’s mandatory. Fair enough. When you run this, you’ll need to provide this information. The next couple of lines are more mysterious:

Begin{}
Process

We haven’t gone into process blocks yet, but for now just know this is where the meat of the script belongs. We go over this more in detail in the Learn Scripting in a Month of Lunches (Manning, 2017).

22.1.2 Process block

We haven’t covered Try Catch yet, but it is coming, don’t worry. For now, just know that you will Try to do the thing, and if that doesn’t work you will CATCH the error it throws. Next we see two variables, $comp and $properties.

foreach ($computer in $computers) {
            try {
                $comp = get-adcomputer $computer
                $properties = @{computername = $computer
                                Enabled = $comp.enabled
                                InAD = 'Yes'}
            } 
            catch {
                $properties = @{computername = $computer
                                Enabled = 'Fat Chance'
                                InAD = 'No'}
            }

$Comp is running an Active Directory command to see if the computer exists, and if it does, it will store the AD information in the $comp variable. $Properties is a hash table that we have created that stores some information that we need that includes the ComputerName, Enabled, and if it’s in AD or not.

The remainder of our script takes our hash table that we created and turns that into a PS custom object, then writes it to the screen with Write-Output.

finally {
           $obj = New-Object -TypeName psobject -Property $properties
           Write-Output $obj
         }

Above and beyond

What would we need to change to write this to a text file or a CSV file?

22.2 It’s a line-by-line examination

The process in the previous section is a line-by-line analysis of the script, and that’s the process we suggest you follow. As you progress through each line, do the following:

  • Identify variables, try to figure out what they’ll contain, and write that down on a piece of paper. Because variables are often passed to command parameters, having a handy reference of what you think each variable contains will help you predict what each command will do.

  • When you run across new commands, read their help and try to understand what they’re doing. For Get- commands, try running them—plugging in any values that the script passes in variables to parameters—to see what output is produced.

  • When you run across unfamiliar elements, such as if or [environment], consider running short code snippets inside a virtual machine to see what those snippets do (using a VM helps protect your production environment). Search for those keywords in help (using wildcards) to learn more.

Above all, don’t skip a single line. Don’t think, “Well, I don’t know what that does, so I’ll just keep going.” Stop and find out what each line does, or what you think it does. That helps you figure out where you need to tweak the script to meet your specific needs.

22.3 Lab

Listing 22.2 shows a complete script. See if you can figure out what it does and how to use it. Can you predict any errors that this might cause? What might you need to do in order to use this in your environment?

Note that this script should run as is (you may have to run it as administrator to access the security log), but if it doesn’t run on your system, can you track down the cause of the problem? Keep in mind that you’ve seen most of these commands, and for the ones you haven’t, there are the PowerShell help files. Those files’ examples include every technique shown in this script.

Listing 22.2 Get-LastOn.ps1

function get-LastOn {
    <#
    .DESCRIPTION
    Tell me the most recent event log entries for logon or logoff.
    .BUGS
    Blank 'computer' column
    .EXAMPLE
    get-LastOn -computername server1 | Sort-Object time -Descending | 
    Sort-Object id -unique | format-table -AutoSize -Wrap
    ID              Domain       Computer Time                
    --              ------       -------- ----                
    LOCAL SERVICE   NT AUTHORITY          4/3/2020 11:16:39 AM
    NETWORK SERVICE NT AUTHORITY          4/3/2020 11:16:39 AM
    SYSTEM          NT AUTHORITY          4/3/2020 11:16:02 AM
    Sorting -unique will ensure only one line per user ID, the most recent.
    Needs more testing
    .EXAMPLE
    PS C:Usersadministrator> get-LastOn -computername server1 -newest 10000
     -maxIDs 10000 | Sort-Object time -Descending |
     Sort-Object id -unique | format-table -AutoSize -Wrap
    ID              Domain       Computer Time
    --              ------       -------- ----
    Administrator   USS                   4/11/2020 10:44:57 PM
    ANONYMOUS LOGON NT AUTHORITY          4/3/2020 8:19:07 AM
    LOCAL SERVICE   NT AUTHORITY          10/19/2019 10:17:22 AM
    NETWORK SERVICE NT AUTHORITY          4/4/2020 8:24:09 AM
    student         WIN7                  4/11/2020 4:16:55 PM
    SYSTEM          NT AUTHORITY          10/18/2019 7:53:56 PM
    USSDC$          USS                   4/11/2020 9:38:05 AM
    WIN7$           USS                   10/19/2019 3:25:30 AM
    PS C:Usersadministrator>
    .EXAMPLE
    get-LastOn -newest 1000 -maxIDs 20 
    Only examines the last 1000 lines of the event log
    .EXAMPLE
    get-LastOn -computername server1| Sort-Object time -Descending | 
    Sort-Object id -unique | format-table -AutoSize -Wrap
    #>
    param (
            [string]$ComputerName = 'localhost',
            [int]$MaxEvents = 5000,
            [int]$maxIDs = 5,
            [int]$logonEventNum = 4624,
            [int]$logoffEventNum = 4647
        )
        $eventsAndIDs = Get-WinEvent -LogName security -MaxEvents $MaxEvents 
       -ComputerName $ComputerName | 
        Where-Object {$_.id -eq $logonEventNum -or `
        $_.instanceid -eq  $logoffEventNum} | 
        Select-Object -Last $maxIDs -Property TimeCreated,MachineName,Message
        foreach ($event in $eventsAndIDs) {
            $id = ($event | 
            parseEventLogMessage | 
            where-Object {$_.fieldName -eq "Account Name"}  | 
            Select-Object -last 1).fieldValue
            $domain = ($event | 
            parseEventLogMessage | 
            where-Object {$_.fieldName -eq "Account Domain"}  | 
            Select-Object -last 1).fieldValue
            $props = @{'Time'=$event.TimeCreated;
                'Computer'=$ComputerName;
                'ID'=$id
                'Domain'=$domain}
            $output_obj = New-Object -TypeName PSObject -Property $props
            write-output $output_obj
        }  
    }
    function parseEventLogMessage()
    {
        [CmdletBinding()]
        param (
            [parameter(ValueFromPipeline=$True,Mandatory=$True)]
            [string]$Message 
        )    
        $eachLineArray = $Message -split "`n"
        foreach ($oneLine in $eachLineArray) {
            write-verbose "line:_$oneLine_"
            $fieldName,$fieldValue = $oneLine -split ":", 2
                try {
                    $fieldName = $fieldName.trim() 
                    $fieldValue = $fieldValue.trim() 
                }
                catch {
                    $fieldName = ""
                }
                if ($fieldName -ne "" -and $fieldValue -ne "" ) 
                {
                $props = @{'fieldName'="$fieldName";
                        'fieldValue'=$fieldValue}
                $output_obj = New-Object -TypeName PSObject -Property $props
                Write-Output $output_obj
                }
        }
    }
Get-LastOn

22.4 Lab answer

The script file seems to define two functions that won’t do anything until called. At the end of the script is a command, Get-LastOn, which is the same name as one of the functions, so we can assume that’s what is executed. Looking at that function, you can see that it has numerous parameter defaults, which explains why nothing else needs to be called. The comment-based help also explains what the function does. The first part of this function is using Get-WinEvent:

$eventsAndIDs = Get-WinEvent -LogName security -MaxEvents $MaxEvents | 
  Where-Object { $_.id -eq $logonEventNum -or $_.id -eq $logoffEventNum } | 
  Select-Object -Last $maxIDs -Property TimeCreated, MachineName, Message

If this were a new cmdlet, we’d look at help and examples. The expression seems to be returning a user-defined maximum of events. After looking at the help for Get-WinEvent, we see that the parameter -MaxEvents will return the maximum number of events sorted from newest to oldest. Therefore, our variable of $MaxEvents comes from a parameter and has a default value of 5000. These event logs are then filtered by Where-Object, looking for two event log values (event IDs of 4627 and 4647), also from the parameter.

Next it looks like something is done with each event log in the foreach loop. Here’s a potential pitfall: In the foreach loop, it looks like other variables are getting set. The first one is taking the event object and piping it to something called parseEventmessage. This doesn’t look like a cmdlet name, but we did see it as one of the functions. Jumping to it, we can see that it takes a message as a parameter and splits each one into an array. We might need to research the –Split operator.

Each line in the array is processed by another foreach loop. It looks like lines are split again, and there is a try/catch block to handle errors. Again, we might need to read up on that to see how it works. Finally, there is an if statement, where it appears that if the split-up strings are not empty, then a variable called $props is created as a hash table or associative array. This function would be much easier to decipher if the author had included some comments. Anyway, the parsing function ends by calling New-Object, another cmdlet to read up on.

This function’s output is then passed to the calling function. It looks like the same process is repeated to get $domain.

Oh, look, another hash table and New-Object, but by now we should understand what the function is doing. This is the final output from the function and hence the script.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.65.61