Tag Archives: Function

A Function for my Functions

I’m in the process of working on a new advanced function template for work. It’ll be the 2.0 version of this: http://tommymaynard.com/function-logging-via-write-verbose-2016. The problem I have with the current version of the template is that I could never only log to a file, without writing to the host program, too. In the end, I want a way to do one of four different things when I run a function written using the template. They are (1) not log anything, (2) log to the screen only, (3) log to a file only (this is the addition I’m after), or (4) log to both the screen and a file simultaneously. This is nearing completion, but one of the downsides to a well thought out and robust advanced function template is that it’s getting, really long. I honestly believe I’m at over 100 lines now (the 1.x versions are in the 70ish line territory).

So, I’m sitting around and thinking of a way to disguise the template, or add my code to it when it’s done, and so I wrote the following pieces of PowerShell. No saying that I’ll ever use this, but it seemed worthy enough to share here. I’m out to automatically add my code to the template, so I don’t have to code around the template’s standard code that allows for the logging.

This first example begins the process of creating a new function named New-FunctionFromTemplate. It’s set to accept three parameters: Begin, Process, and End. For those writing advanced functions, this should be easily recognizable as to why. It’s going to stuff whatever values are supplied to these parameters into these respective sections in the function template, that it displays when it’s done executing. You write the code, and it’ll place that code into the function template, via the New-FunctionFromTemplate function.

Function New-FunctionFromTemplate {
    Param(
        [string]$Begin,
        [string]$Process,
        [string]$End
    )
...
}

Next, I’ve included the remainder of this function. It’s a here-string that includes the function template design and layout, with a place for each variable within it. These variables will be replaced with whatever we send into the New-FunctionFromTemplate function when it’s invoked.

Function New-FunctionFromTemplate {
    Param(
        [string]$Begin,
        [string]$Process,
        [string]$End
    )   

    @"
Function ___-________ {
    [CmdletBinding()]
    Param (
    )

    Begin {
        $Begin
    } # End Begin.

    Process {
        $Process
    } # End Process.

    End {
        $End
    } # End End.
} # End Function: ___-________.
"@
}

Now that we’ve defined our function, let’s use it. As a bit of a shortcut, and as a way to make things a bit more readable, we’ll create a parameter hash table and splat it to the New-FunctionFromTemplate function. The below example could’ve been written as New-FunctionFromTemplate -Begin ‘This is in the Begin block.’ -Process ‘This is in the Process block.’ … etc., but I’m opted to not to that, to make things a bit easier to read and comprehend.

$Params1 = @{
    Begin = 'This is in the Begin block.'
    Process = 'This is in the Process block.'
    End = 'This is in the End block.'
}
New-FunctionFromTemplate @Params1

Below is the output the above commands create.

Function ___-________ {
    [CmdletBinding()]
    Param (
    )

    Begin {
        This is in the Begin block.
    } # End Begin.

    Process {
        This is in the Process block.
    } # End Process.

    End {
        This is in the End block.
    } # End End.
} # End Function ___-________.

This thing is, someone’s not typically only going to add a single string — a single sentence, if you will — inside a Begin, Process, or End block. They’re much more likely to add various language constructs and logic, and comments. Here’s a second example to help show how we would go about adding more than just a single string, using a here-string for two of the three blocks.

$Params2 = @{
    Begin = @'
If ($true) {
            'It is true.'
        } Else {
            'It is false.'
        }
'@
    Process = @'
'This is in the Process block.'
'@
    End = @'
If ($false) {
            'It is true.'
        } Else {
            'It is false.'
        }
'@
}
New-FunctionFromTemplate @Params2

When the above code is run, it produces the below output. It includes both our template structure, as well as, the code we want inside each of the blocks. In case you didn’t catch it right away, there’s a bit of a caveat. The first line is right up against the left margin. Even though, it’ll drop everything into it’s proper place — a single tab to the right of the beginning of each block. After that first line, you have to be the one to monitor your code placement, so that when it’s combined with the template, all the indentations line as expected.

Function ___-________ {
    [CmdletBinding()]
    Param (
    )

    Begin {
        If ($true) {
            'It is true.'
        } Else {
            'It is false.'
        }
    } # End Begin.

    Process {
        'This is in the Process block.'
    } # End Process.

    End {
        If ($false) {
            'It is true.'
        } Else {
            'It is false.'
        }
    } # End End.
} # End Function: ___-________.

And, that’s it. While I put this together, I’ve yet to implement it and truly code separately from my current template. We’ll see what the future holds for me, but at least I know I have this option, if I decide it’s really time to use it. Enjoy your Thursday!

Years Too Late: My First ISE Snippet

Every time I start to write a new PowerShell function, I manually write the same block of text. No idea how many times I’ve done it, but I’ve finally decided to stop. Today, I wrote it for the last time.

$Text = @'
Function ___-_________ {
    [CmdletBinding()]
    Param (
    )

    Begin {
    } # End Begin.

    Process {
    } # End Process.

    End {
    } # End End.
} # End Function: ___-_________.
'@

I’ve known about ISE snippets for some time, but haven’t taken a minute to get my advanced function included. Well, that finally ended today. With the $Text variable assigned above, I ran the following command.

New-IseSnippet -Title BasicFunction -Description 'Basic Advanced Function.' -Text $Text -Author 'Tommy Maynard'

Well, what did this just do? In the most basic reply to that question, it added a new snippet — a reusable chunk of text — I can add to the ISE anytime I want. All I have to do is press Ctrl + J and select BasicFunction from the available options.

That may be all you need, but I was curious what this really did. To find out, I ran Get-IseSnippet and it returned a path — helpful.

Get-IseSnippet

    Directory: C:\Users\tommymaynard\Documents\WindowsPowerShell\Snippets

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        9/27/2016   0:32 PM            867 BasicFunction.snippets.ps1xml

Then I ran Get-Content on the file to see what it was storing.

Get-Content -Path (Get-IseSnippet).FullName

<?xml version='1.0' encoding='utf-8' ?>
    <Snippets xmlns='http://schemas.microsoft.com/PowerShell/Snippets'>
        <Snippet Version='1.0.0'





<Header>
                <Title>BasicFunction</Title>
                <Description>Basic Advanced Function.</Description>
                <Author>Tommy Maynard</Author>
                <SnippetTypes>
                    <SnippetType>Expansion</SnippetType>
                </SnippetTypes>
            </Header>






            <Code>
                <Script Language='PowerShell' CaretOffset='0'>
                    <![CDATA[Function ___-_________ { [CmdletBinding()] Param ( ) Begin { } # End Begin. Process { } # End Process. End { } # End End. } # End Function: ___-_________.]]>
                </Script>
            </Code>

    </Snippet>
</Snippets>

So, there it is. Not only do I have my basic function snippet the next time I need it, I know that New-IseSnippet is writing an XML (.ps1xml) file to my Snippets directory in my Documents/WindowsPowerShell directory in my local profile. The date on my snippet and the directory indicate they were both created when I ran this command. I told you I hadn’t used snippets before.

Me being me, I ran Get-Command against New-IseSnippet and guess what? It’s a function; it’s not compiled code. Let’s take a look at it; let’s find where it decides whether to create the directory, or not.

Get-Command -Name New-IseSnippet

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        New-IseSnippet                                     1.0.0.0    ISE

(Get-Command -Name New-IseSnippet).ScriptBlock

While I didn’t include the entire results of the last command, I’ll include what’s important for how it determined whether or not to create the Snippets directory. In this first part, the function creates a $snippetPath variable. In it, it stores the current user’s WindowsPowerShell directory path. Before writing that to the variable, Join-Path appends “Snippets” — the child path — to the end. That means that in the end, the $snippetPath variable contains C:\Users\tommymaynard\Documents\WindowsPowerShell\Snippets.

$snippetPath = Join-Path (Split-Path $profile.CurrentUserCurrentHost) "Snippets"

In this section of the function, it runs Test-Path against $snippetPath, to determine if the path exists, or not. This cmdlet returns $true or $false depending on whether the path exists.

if (-not (Test-Path $snippetPath))
{
    $null = mkdir $snippetPath
}

If the path doesn’t exist, thanks to the -not, it executes the mkdir function against the path, and the directory is created. The next time New-IseSnippet function is run, the directory will already exists and this part of the function won’t be run.

Well, that’s it. I’m already looking forward to pressing Ctrl + J the next time I need to start a new advanced function.

Run Background Commands after Every Command

For a complete introduction to this post, please read the first two paragraphs at PowerShell.org: http://powershell.org/wp/2015/10/12/run-background-commands-after-every-command.

There’s a part II, now. Be sure to read that when you’re done here: http://tommymaynard.com/quick-learn-run-background-commands-after-every-command-part-ii-2015.

You know the prompt, it often looks like this: PS C:\> or this: PS>, or even this [DC05]: PS C:\Users\tommymaynard\Documents>, when you’re interactively remoting to another computer. That built-in function determines the appearance of your prompt. I’ve seen several different modifications of the prompt. You can add the the date and time to the prompt, or like the example in the about_Prompt help file, you can add ‘Hello World’ every time the prompt is displayed. Doable, yes, but helpful, probably not.

This post isn’t about changing the prompt’s appearance, but instead about doing something else inside the prompt function. What the prompt looks like is defined by a function called, prompt — you guessed it. Take a look at your current prompt, as in the example below.

PS> (Get-ChildItem -Path Function:\prompt).ScriptBlock
"PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "
# .Link
# http://go.microsoft.com/fwlink/?LinkID=225750
# .ExternalHelp System.Management.Automation.dll-help.xml

This is the standard prompt function beginning in PowerShell 3.0 up though PowerShell 5.0, the current version of PowerShell, as of this writing.

This function is invoked each time a command is run and the prompt is displayed again. What if we added additional code inside this function? It would also be run every time a new prompt was displayed. My initial idea was to add the current directory to the WindowTitle. If you don’t know what the WindowTitle is, it’s the text at the top of the console host that says “Administrator: Windows PowerShell,” or “Windows PowerShell,” when in a non-elevated host.

I tried the current directory and quickly realized it took more time to look up at the WindowTitle, from the current prompt, than to leave the default prompt alone and get this information from there. The next idea was to add the date and time to the WindowTitle. This way I would know the time the last prompt was displayed. This is practically the time the last command ended. It seemed useful… for about a minute. I finally decided on putting an indicator in the WindowTitle so that I would know if there were any background jobs or not. I seem to open and close new consoles all day long, and knowing if I was about to dump a console with an active job, whether it was still running or not, or whether it still had data, seemed useful.

Let’s walk though what I did to get this to happen. Before we do that, let’s compare the two images below. The first one shows the standard WindowTitle when there’s no job, and the [Job] indicator when there is a job. We’re going to add a few lines of PowerShell to make this happen.

run-background-commands-after-every-command01run-background-commands-after-every-command02

The first thing I did was to define a function in my profile script ($PROFILE). This function would overwrite the default prompt function, as PowerShell reads in the profile script, after it has already created the default prompt.

Function prompt {

}

Next, I enter the default, prompt text. Notice I’m not changing how the prompt is displayed.

Function Prompt {
    "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "
}

Following this, I added the first piece of conditional logic. We test for a variable called $OriginalTitle. This variable will not exist the first time the prompt function is run, as the variable is created inside this If statement. It effectively ends up holding whatever was in WindowTitle when the console host was first opened. You’ll soon see how we reuse this value.

Before we go on, I should mention that I’ve made this a globally-scoped variable. This is because the variable needs to retain its value outside the function, and it needs to exist each time the function ends. Because of the way scope works, when we enter the function the second time and it can’t find $OriginalText, the function will go up a level and check for the existence of the variable in the parent scope, which in this case is the global scope.

Function Prompt {
    If (-Not($OriginalTitle)) {
        $Global:OriginalTitle = $Host.UI.RawUI.WindowTitle
    }

    "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "
}

In the next part of the function, I added a check to see if the Get-Job cmdlet returns anything. If it does, there are current background jobs, and if it doesn’t, then there are no current background jobs. We’ll start with what happens when there aren’t any jobs, first. In the Else portion of this If-Else statement, we set the current WindowTitle to whatever is stored in the $OriginalTitle variable. This ensures that the WindowTitle looks just like it did when we initially started the console host and there were no background jobs.

Function Prompt {
    If (-Not($OriginalTitle)) {
        $Global:OriginalTitle = $Host.UI.RawUI.WindowTitle
    }

    If (Get-Job) {
        # Coming
    } Else {
        $Host.UI.RawUI.WindowTitle = $OriginalTitle
    }

    "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "
}

So what happens when there are jobs? In this final portion of the function, we have an embedded If statement. If the current WindowTitle doesn’t already include the string [Job], then we add it. If it’s already there, then we leave it alone, and write the prompt.

Function Prompt {
    If (-Not($OriginalTitle)) {
        $Global:OriginalTitle = $Host.UI.RawUI.WindowTitle
    }

    If (Get-Job) {
        If ($Host.UI.RawUI.WindowTitle -NotLike '[Job]*') {
            $Host.UI.RawUI.WindowTitle = "[Job] $OriginalTitle"
        }
    } Else {
        $Host.UI.RawUI.WindowTitle = $OriginalTitle
    }

    "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "
}

Thanks to the prompt function, we have another way to do something the moment the console is opened, and now, every time a new command is entered and a new prompt is displayed in the host. This little project has already got me wondering about some other things I may want to try with this function. I might expand what I’ve done so far, and provide indicators when there’s more than one job ([Job] vs. [Jobs]), if the jobs are still running, and perhaps if they have more data (whether it hasn’t been received yet, or was kept when it was received).

Keep in mind that this function runs after every command, successful or otherwise. Do your best not to overload the actions in the function. The default prompt comes in under a millisecond. My prompt, with the code above added, comes in at an average of 1 millisecond. If you end up doing too much in the function, you might end up waiting longer than you’re used to, making your additions to the function questionable.

Have fun, and thanks to everyone for reading this post.

Get a Locally Declared Function into a Remote Session

A part of why I write, is to have a place to store things I’m probably going to forget the moment I need them. Notice the search feature to the right. I’d be surprised to find out that someone has used it more than me.

That brings us to today’s topic: getting a locally declared function into a remote session. I wrote a recent post about getting variables into a remote sessions — you can read that here: http://tommymaynard.com/quick-learn-getting-local-variables-into-remote-sessions-2015 — and thought that getting a local function into a remote session, would make for a good followup.

First, let’s start with a function. This is a mildly complex function to use for an example, but by using it, I’ll be ensuring I can find it later, too. This function is a wrapper for the command: Dism /online /Get-Features. It takes the text produced by that command and returns the results as objects. I wrote this as part of an answer to a thread on the Microsoft Technet Forums: https://social.technet.microsoft.com/Forums/scriptcenter/en-US/972d87a9-9930-4f64-8592-5406f5fab8f4/dism-online-getfeatures-with-where-clause?forum=ITCG#01aeebe3-01b5-49cd-a8dd-1b1ce0352c8b.

Function Get-DismFeatures {
    [CmdletBinding()]
    Param ()

    Begin {
        $Results = Dism /online /Get-Features
        $Results = $Results[8..($Results.Count - 3)] | Where-Object {$_ -ne ''}

        $Feature = $Results | Select-String 'Feature'
        $State = $Results | Select-String 'State'
        $ResultsCounter = $Results.Count/2
    } # End Begin.

    Process {
        for ($i = 0; $i -lt $ResultsCounter; $i++) { 
            [pscustomobject]@{
                Feature = $Feature[$i].ToString().Split(':')[-1].Trim()
                State = $State[$i].ToString().Split(':')[-1].Trim()
            }
        }
    } # End Process.

    End {
    } # End End.
} # End Function.

Again, we could’ve use a much simpler function. Once a function is declared, we run the actions inside the function by invoking it — entering its name into the console. To run this locally, we’d just enter Get-DismFeatures, press Enter, and it would produce the results below. I’ve greatly shortened the results to save space.

PS C:\> Get-DismFeatures

Feature                                                     State
-------                                                     -----
Microsoft-Hyper-V-All                                       Enabled
Microsoft-Hyper-V-Tools-All                                 Enabled
Microsoft-Hyper-V                                           Enabled
Microsoft-Hyper-V-Management-Clients                        Enabled
Microsoft-Hyper-V-Management-PowerShell                     Enabled
Printing-Foundation-Features                                Enabled
Printing-Foundation-LPRPortMonitor                          Disabled
Printing-Foundation-LPDPrintService                         Disabled
...

To run this on a remote computer, or computers, we would need to ensure PS Remoting is available and working. You can test this with your own computer, or with a remote computer. Yes, you can use your local computer to determine if PS Remoting is working from, and into, your computer. Here’s three different examples of using my own computer, and one example of using a remote one.

PS> Invoke-Command -Computer localhost -ScriptBlock {$env:COMPUTERNAME}
TOMMYSCPU
PS> Invoke-Command -Computer . -ScriptBlock {$env:COMPUTERNAME}
TOMMYSCPU
PS> Invoke-Command -ScriptBlock {$env:COMPUTERNAME}
TOMMYSCPU
PS> Invoke-Command -Computer DC05 -ScriptBlock {$env:COMPUTERNAME}
DC05

Okay, so on to taking the function to a remote computer. Here’s how we do that (also with shorten results). Notice the placement of the dollar sign ($). This is different that we saw when taking locally declared variables to the remote session.

PS> Invoke-Command -ComputerName DC05 -ScriptBlock ${function:Get-DismFeatures}

Feature        : NetFx4ServerFeatures
State          : Enabled
PSComputerName : DC05
RunspaceId     : 72dc5b41-31b8-22b1-b5b1-29eae648123a

Feature        : NetFx4
State          : Enabled
PSComputerName : DC05
RunspaceId     : 72dc5b41-31b8-22b1-b5b1-29eae648123a
...

And, that’s it.

If you’re like me, you’re sitting back now and wondering, “Why not include the ability to run the function against remote computers, inside the function?” That’s an option, and often employed to make a function useful on both local and remote systems. Here’s the thing: You may not always write the tools you use, so having a way to use one that was written without a way to run it remotely, might be helpful one day.