Advanced Windows PowerShell Scripting Video Training

Advanced Windows PowerShell Scripting Video Training
Advanced Windows PowerShell Scripting Video Training

Tuesday, March 12, 2019

Where to Create a Constant in an Advanced Function?


This week I am delivering a PowerShell Toolmaking class in Phoenix.  Right now we are studying advanced functions.  While talking about the BEGIN, PROCESS, and END block, one of my future PowerShell Rock Stars gave me a great idea.  Where should a constant be created in an advanced function>

A constant is, by definition, something that cannot be changed after it is created.  So, here is how you create one.

$CONST = New-Variable -Name Constant1 -Value 10 -Option Constant

You cannot modify this constant with Set-Variable -Force and you cannot remove this constant with Remove-Variable -Force.  That is the idea.  You need to close your current scope of memory to get rid of it.

The BEGIN, PROCESS, and END blocks of an advanced function have unique capabilities.

The BEGIN block is executed when your cmdlet receives the first object in the pipeline, but before the PROCESS block is executed.  It is only ran once to allow you the opportunity to do any set up operations that the cmdlet will need to do before processing objects.

The PROCESS block is executed once for each object that is piped into your cmdlet.

The END block is executed only once when there are no more objects left in the pipeline.  This allows you to do any cleanup operations after all the work is done.

So the question is, “Do you create a constant in the BEGIN, PROCESS, or END blocks?”  Well, we are going to just not even try the END block because your constant will not be available to any other part of your code.  Let’s try building the constant in the PROCESS block.

Function Test1 {
[CmdletBinding()]
Param (
    [parameter(ValueFromPipeline=$true)]
    $Param1
)
BEGIN {}
PROCESS {$CONST = New-Variable -Name Constant1 -Value 10 -Option Constant}
END {}

}

10, 20, 30 | Test1

At this point you will receive an error because you are attempting to create multiple constants of the same name in the same scope of memory.  The first iteration worked, but all others failed.  Now for the BEGIN block

Function Test2 {
[CmdletBinding()]
Param (
    [parameter(ValueFromPipeline=$true)]
    $Param1
)
BEGIN {$CONST = New-Variable -Name Constant1 -Value 10 -Option Constant}
PROCESS {}
END {}

}

10, 20, 30 | Test2

Now it worked.  The BEGIN block is executed only once.  The constant is placed in the cmdlets scope of memory only once and it is available to the rest of the code.



Tuesday, January 15, 2019

What Will a Cmdlet Accept Through the PowerShell Pipeline?



Happy New Year!

Ok, that is a bit late.  I'm getting around to catching up on some questions from my classes last year.  Here is a good one.

What Will a Cmdlet Accept Through the PowerShell Pipeline? Well, that is a good question.  How a object is piped from one cmdlet to another is a touchy subject in my PowerShell classes.  This extremely powerful feature needs to be used, but the research that goes into it for new PowerShell developers can be a bit complex.  To help out, I built a cmdlet to read the help files of a cmdlet or script and determine what parameters accept pipeline input ByValue or ByPropertyName and what data type they require.  All of these are required to help the developer understand if they can pipe their objects to a cmdlet.

Here you go!  Have fun with it.


Function Get-PipelineInfo {
[CmdletBinding()]
Param (
    [String]
    $Cmdlet
)

Write-Verbose "Pipeline informaiton for $Cmdlet."
(Get-Help $Cmdlet).Parameters.Parameter |
    Where-Object PipelineInput -ne "False" |
    Select-Object -Property Name ,
        @{N = "ByValue" ; E = { If ($_.PipelineInput -Like "*ByValue*") {$True}
                                Else {$False} }},
        @{N = "ByPropertyName" ; E = { If ($_.PipelineInput -Like "*ByPropertyName*") {$True}
                                        Else {$False} }},
        @{N = "Type"; E = {$_.Type.Name}}

<#
.SYNOPSIS
Gets the pipeline information for cmdlets.

.DESCRIPTION
Reads the help files of PowerShell scripts and cmdlets and extracts the
information on all parameters that accept information from the PowerShell
pipeline.

.PARAMETER Cmdlet
The cmdlet you want to get pipeline information on.

.EXAMPLE
Get-PipelineInfo -Cmdlet 'Get-Process'

name         pipelineInput         Type      Cmdlet    
----         -------------         ----      ------    
ComputerName True (ByPropertyName) String[]  Get-Process
Id           True (ByPropertyName) Int32[]   Get-Process
InputObject  True (ByValue)        Process[] Get-Process
Name         True (ByPropertyName) String[]  Get-Process

.NOTES
===============================================================================
== Cmdlet: Get-PipelineInfo                                                  ==
== Author: Jason A. Yoder                                                    ==
== Company: MCTExpert of Arizona                                             ==
== Date: January 15, 2019                                                    ==
== Copyright: All rights reserved.                                           ==
== Version: 1.0.0.0                                                          ==
== Legal: The user assumes all responsibility and liability for the usage of ==
== this PowerShell code.  MCTExpert of Arizona, Its officers, shareholders,  ==
== owners, and their relatives are not liable for any damages.  As with all  ==
== code, review it and understand it prior to usage.  It is recommended that ==
== this code be fully tested and validated in a test environment prior to    ==
== usage in a production environment.                                        ==
==                                                                           ==
== Does this code make changes: NO                                           ==
===============================================================================
#>
} # END: Function Get-PipelineInfo








Friday, February 2, 2018

Overwriting a PowerShell Constant

I have always said that one of the greatest benefit to teaching PowerShell (and Windows) is that different people bring different ideas to the table.  Things get fun for me when someone looks at what we are doing from a different angle and asked an interesting question.

This week’s class in Fort Wayne produced one of those questions.  We were looking at some of the options that are available to use with creating variables with the New-Variable cmdlet.  In particular, we were looking at constants.  Let’s build one.

PS C:\> New-Variable -Name Test1 -Value ([Bool]$True) -Option Constant

Now let’s take a look at the variable object.
Name        : Test1
Description :
Value       : True
Visibility  : Public
Module      :
ModuleName  :
Options     : Constant
Attributes  : {}


We can see from the Options property that we have created a constant.  We are going to attempt to change that value of this constant to FALSE.

PS C:\> Set-Variable -Name Test1 -Value $false
Set-Variable : Cannot overwrite variable Test1 because it is read-only or constant.
At line:1 char:1
+ Set-Variable -Name Test1 -Value $false
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (Test1:String) [Set-Variable], SessionStateU
   nauthorizedAccessException
    + FullyQualifiedErrorId : VariableNotWritable,Microsoft.PowerShell.Commands.SetVar
   iableCommand

This is what we expected.  By definition, a constant cannot be changed.  We also attempted to change it with the –Force parameter.

PS C:\> Set-Variable -Name Test1 -Value $false -Force
Set-Variable : Cannot overwrite variable Test1 because it is read-only or constant.
At line:1 char:1
+ Set-Variable -Name Test1 -Value $false -Force
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (Test1:String) [Set-Variable], SessionStateU
   nauthorizedAccessException
    + FullyQualifiedErrorId : VariableNotWritable,Microsoft.PowerShell.Commands.SetVar
   iableCommand

Again, the expected results.  Well, this is where that question comes into play.  What if you re-cast the variable?  OK, let’s give this a try.

PS C:\> [Bool]$Test1 = $False

PS C:\> $Test1
False

Wait… What?  You cannot even get rid of a constant with Remove-Variable but here we changed it.  OK, did we really change the value or did it delete the variable and then recreate it?  Here is another test.

PS C:\> Set-Variable -Name Test3 -Value ([Bool]$True) -Option Constant -Description "This is a test"


Here we included a description which you can see in the variable objects properties.

Name        : Test3
Description : This is a test
Value       : True
Visibility  : Public
Module      :
ModuleName  :
Options     : Constant
Attributes  : {}

We are going to change this variable using the same successful method from above.

PS C:\> [Bool]$Test3 = $False

PS C:\> $Test3
False


And now let’s look at the properties to see if the description is still there.
PS C:\> Get-Variable -Name Test3 | Select-Object -Property *


PSPath        : Microsoft.PowerShell.Core\Variable::Test3
PSDrive       : Variable
PSProvider    : Microsoft.PowerShell.Core\Variable
PSIsContainer : False
Name          : Test3
Description   : This is a test
Value         : False
Visibility    : Public
Module        :
ModuleName    :
Options       : Constant
Attributes    : {System.Management.Automation.ArgumentTypeConverterAttribute}



The description is still there.  So, I guess there is a way to change the value of a constant without restarting PowerShell

Wednesday, January 31, 2018

What are Positional Parameters?

Often while teaching PowerShell, we get into a discussion about how someone, usually me, types this:

Get-Help Get-Date

Instead of

Get-Help –Name Get-Date

PowerShell parameters utilize positioning.  Good authors of cmdlets will determine which parameter will be the most frequently used and put that parameter in the first position.  That means if the user types a cmdlet, they can immediately provide the data for that parameter without calling the parameter name.  Take a look at the –Name parameter of Get-Help
-Name
    Gets help about the specified command or concept. Enter the name of a cmdlet, function, provider,
    script, or workflow, such as `Get-Member`, a conceptual topic name, such as `about_Objects`, or an
    alias, such as `ls`. Wildcard characters are permitted in cmdlet and provider names, but you
    cannot use wildcard characters to find the names of function help and script help topics.
   
    To get help for a script that is not located in a path that is listed in the Path environment
    variable, type the path and file name of the script.
   
    If you enter the exact name of a help topic, Get-Help displays the topic contents. If you enter a
    word or word pattern that appears in several help topic titles, Get-Help displays a list of the
    matching titles. If you enter a word that does not match any help topic titles, Get-Help displays
    a list of topics that include that word in their contents.
   
    The names of conceptual topics, such as `about_Objects`, must be entered in English, even in
    non-English versions of Windows PowerShell.
   
    Required?                    false
    Position?                    0
    Default value                None
    Accept pipeline input?       True (ByPropertyName)
    Accept wildcard characters?  false

Two things to take note of.  First of all, the type of data that this parameter accepts is [String].  The second is the value of Position which is zero.  That means if the user types the cmdlet Get-Help and then a value of the type string, that value will be the argument for the –Name parameter. 

I often stress the need for full command syntax in scripts so everyone knows what parameters you are using but I am also guilty of using positional parameters for my more common cmdlets like Get-Help and Where-Object.  Here is some code to help you see the parameter in the first position and what type of data it expects.  Just be forwarded, it will load all of your modules into memory.
$Commands = Get-Command

ForEach ($CMD in $Commands) {
    $Obj = [PSCustomObject]@{
        'Cmdlet' = $CMD.Name
        'PositionOne' = (($CMD | Get-Help).parameters.parameter | where position -eq 0).Name
        'Type' = (($CMD | Get-Help).parameters.parameter| where position -eq 0).Type.Name
        }

    Write-OutPut $Obj
}


Monday, January 29, 2018

How to start a PowerShell Script from a Batch File

How to start a PowerShell Script from a Batch File

In last week’s PowerShell class in Phoenix, we had a last minute question.  It involved trying to simplify the launching of a PowerShell script for users.  Having end users working with PowerShell has long been a cumbersome task.  End users like a GUI.  We can put a GUI interface on top of our code, but it is difficult to do manually or you need a third party solution.  When you build a GUI, it also takes an additional skill set that most IT Pros do not have.

We decided to go with a batch file.  Yes, I know.  Old tech but we will give it new life.  Here is our test code for this project. We saved this file as c:\ps\Test1.ps1.

Write-Host "I work!!!" -BackgroundColor DarkMagenta

Yes, I know.  Not exactly exciting.  The purpose of this is to get it to launch with a batch file.

We looked at the PowerShell.exe Command-Line Help (https://docs.microsoft.com/en-us/powershell/scripting/core-powershell/console/powershell.exe-command-line-help?view=powershell-5.1) to see how to launch PowerShell with a script from the command line at the same time.  We came up with:

PowerShell.exe –File C:\PS\Test1.ps1

We saved this command line into a batch file in the same directory as the script and was able to launch it from a desktop shortcut icon.  Right now, this is a viable option.

What about using parameters?  This is a bit more difficult.  The original objective was to do it from a DOS command prompt, but when we add parameters, the process is just as complex as doing it PowerShell if not more.  Here is our new code.

Param ($ComputerName)
Write-Host "I work!!!" -BackgroundColor DarkMagenta
Write-Host $ComputerName

Again, I know.  Real advanced.  This is what our batch file looks like now:

PowerShell.exe –File C:\PS\Test1.ps1 –ComputerName INDY-DC1
The original goal was to simplify this so the user did not have to type in PowerShell.  At this point, I would actually have the user use PowerShell and turn this script into a cmdlet in an auto-loading module.  To do this new process via batch file, here are the steps:
1.       Open Notepad
2.       Open the batch file in notepad
3.       Manually enter the computer name.
4.       Save the file
5.       Double click the desk shortcut to the batch file.

If this was a cmdlet in an auto-loading module, here is the process:
1.       Open PowerShell
2.       Type CmdletName –ComputerName INDY-DC1
That is it!