Quantcast
Channel: Windows PowerShell - SAPIEN Blog
Viewing all 308 articles
Browse latest View live

PowerShell Studio 2014: UI Changes

$
0
0

In the latest build of PowerShell Studio 2014 (v4.1.71) we made some UI changes to the Ribbon. Particularly the Home tab of the Ribbon:

SNAGHTML29c523

The most notable change is to the Run group. We changed the run and debug icons and rearranged the menu buttons:

image

We separated out the remoting commands into their own menu:

image

 

And we added two new options to the Run menu:

image

You can now access the run selection commands directly from the ribbon.

 

We also updated the Edit group of the Home tab and added a functions menu:

image

The new menu contains the following options:

Insert New Function – Opens the Function Builder so that you can create a new function.

Edit Function – Allows you to edit functions using the Function Builder. We will discuss this new feature in the next article.

 

The Designer tab also had a slight makeover:

SNAGHTML342e51

The designer commands are now separated into their own groups and we changed a few buttons sizes.

Hopefully these UI changes will make some of the commands more discoverable and easier to access.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Technorati Tags: , ,


PowerShell Studio 2014: Editing Functions

$
0
0

We improved the Function Builder yet again!

Man, when are you guys going to stop this?

Never.

With the 4.1.71 build of PowerShell Studio 2014, you now have the option to edit existing functions within the Function Builder!

Ok cool, how do I edit my existing functions?

Simple, you can edit existing functions using the following methods:

1. Using the new Functions menu in the updated ribbon:

image

2. Use the editor’s context menu:

image

3. Press (Ctrl + Shift + Alt+ E) using the keyboard.

4. Use the Functions explorer:

We added a toolbar to the Function Builder so you can insert a new function or edit the selected function.

image

Now let’s look at the updated Function Builder:

SNAGHTML5a1823

The first thing you will notice is that the fields are pre-populated with the existing function’s information. Updating a function is now easier than ever, especially when dealing with complex parameter sets. In addition, we updated some of the help messages to better explain the fields.

Next we updated the Parameter editor dialog:

SNAGHTML5f57b2

You now have the option to add a new Parameter Set within the Parameter editor:

image

You can now specify a default value for the parameter:

image

When the parameter is set to Mandatory the default value field is disabled.

Once you are satisfied with your changes to the function, the Function Builder will update the definition as well as its comment-based help if help information is available (see below).

 

Special Considerations:

- Renaming Functions

When you rename an existing function in the Function Builder, PowerShell Studio will bring up the Preview Changes dialog:

SNAGHTML720e54

Here you have the option to select which of the function references you wish to update in the script.

-Renaming Parameters

Build v4.1.71 will not update references to renamed parameters.

If you want to rename parameters use the Rename Refactoring instead. Rename Refactoring will ensure your references and their definitions are up-to-date without breaking your script.

http://www.sapien.com/blog/2014/05/01/powershell-studio-2014-rename-refactoring/

The next service build (v4.1.72 and above) will support parameter reference updating.

- Comment-Based Help

The Function Builder will now insert a Comment-Based help block only if you fill in a help field in the Function Builder. This includes all the Description and Synopsis fields.

- Comments in the Parameter Block

If the Function Builder encounters any comments in the function’s parameter block it will automatically assign the comment to the parameter’s description. All parameter comments will be moved to the comment-based help block.

- Name Validation

The Function Builder will now validate the function’s name to prevent duplicates.

- Undo changes

If you are not happy with the changes made by the Function Builder for any reason, you can always undo the changes directly in the editor (Ctrl + Z).

 

We are constantly striving to make PowerShell Studio 2014 the best tool out there. Please let us know what you think by posting on our forums. Your feedback is important to us.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Technorati Tags: , , , ,

June Blender Joins SAPIEN

$
0
0
20,000 feet

For Immediate Release

October 1, 2014

Contact: Ferdinand G. Rios, CEO, frios@sapien.com

June Blender joins SAPIEN Technologies, Inc.

June Blender

June Blender: SAPIEN’s New Technology Evangelist

Napa, California— October 1, 2014SAPIEN Technologies, Inc. is pleased to announce that June Blender is joining their staff as a Technology Evangelist effective today. June will be supporting the SAPIEN customer community with writing, blogging, training, outreach, forum participation, and technical support for Windows PowerShell and other development platforms, as well as SAPIEN products. “We see June as the technical liaison between the community and the SAPIEN development team,” said Ferdinand Rios, CEO of SAPIEN.

“This is a great opportunity for me and a great fit,” June replied. “Customer interaction and technical training, which were always a highlight of my writing job, are now my main focus.”

June worked most recently as a Senior Programming Writer at Microsoft Corporation, where she is best known for her work with the Windows PowerShell product team from 2006-2012, developing the help system and writing the core documentation for versions 1.0 – 3.0. June wrote content for the Azure Active Directory SDK and Azure PowerShell Help, for the Windows Driver Kits, Windows Support Tools, and Windows Resource Kits. She is an Honorary Scripting Guy and a frequent contributor to PowerShell.org.

You can follow June Blender on Twitter at @juneb_get_help, on the SAPIEN blog, on SAPIEN’s ScriptingAnswers.com, and other popular Windows PowerShell, developer, and IT Pro forums.

About SAPIEN Technologies, Inc.

Founded in 1990, SAPIEN Technologies, Inc. is a privately held software and services company headquartered in Napa, California, USA. SAPIEN features popular professional development tools, including PowerShell Studio and PrimalScript, books, training, and support.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Technorati Tags: , , ,

Adding Parameter Sets to a Function

$
0
0

Like many of you, I really live at the command line, because automation is more efficient than single actions. But, as the command line becomes more complex, we need tools to restore our efficiency. The complex syntax of parameter sets in functions is one of those cases. In this post, I learn to use the new Function Editor in PowerShell Studio 2014. I’m not just playing in a GUI. I’ve automated the command line.

——

When I write functions, I often start with the basic command – the one that makes the function work – and then build in features. I might even start with fixed values to test the concept and, after testing, convert them to parameters. Next, I add error handling. And, thinking about errors and the user experience often leads me to add parameter sets.

The syntax for parameter sets and a default parameter set, which requires the CmdletBinding attribute, is tricky. I wrote the about_Function help topics that explain the syntax of these elements, but I still never remember them. Typically, I use snippets for the syntax, or search for an old function to use as a template (dir *ps1 | Select-String DefaultParameterSetName | Select FileName –Unique).

But, in PowerShell Studio 2014, I don’t have to remember any of it. The new Function Editor handles all of the syntax so I can concentrate on my function. Here’s how it works.

I’ll start with a very simple function that gets the value of the HelpUri property of a command. The HelpUri value is the online location of a help topic. It’s actually stored in one of two places, but I want to keep this very simple.

function Get-HelpUri ($Command)
{
    $cmd = Get-Command $Command -ErrorAction SilentlyContinue
    if (!($cmd))
    {
        throw "Cannot find $Command"
    }
    $cmd.HelpUri
}

I still have a lot to do to make this function work correctly, but at this point, I realize that users might want the HelpUri values of all commands in a module. So, I need to add a Module parameter. The Command and Module parameters are exclusive, so I need two parameter sets. But, instead of looking up the syntax for parameter sets, I use the automation in PowerShell Studio 2014.

Here’s the magic. I right click my function and then click Edit Function.

EditFunction1

The Function Builder parses the simple function. I don’t need to enter anything that I’ve already defined.

EditFunction2

 

I click Enable Cmdlet Binding, add a Module parameter, set the types for my parameters, and make them mandatory. I don’t need to think about syntax, just about my function.

AddParameter

 

Next, I add the parameter sets: CmdletSet for the Command parameter and ModuleSet for the Module parameter. I verify that the CmdletSet is selected as the default parameter set. Then, in the Parameters section, I assign each parameter to a parameter set.

AddParmSets

 

Finally, I add the starter help content and output type. (I can do this later, but I like to get a head start.) Then, I click OK.

Wowee!

Result

 

I haven’t spent a minute fooling with syntax. I didn’t have to worry about square brackets, matching parentheses, the exact names of the attributes (is that ParameterSet or ParameterSetName?), or anything other than my function.

Now, I can work on the implementation of my new Module parameter and other details of my function. Later, when I decide to make the parameters take input from the pipeline, I don’t need to worry about the syntax of the ValueFromPipeline parameter attribute. I just right-click my function and click Edit Function again.

The Function Editor is a real boost to my productivity. In a way, the Function Editor does for Windows PowerShell what Windows PowerShell did for the Windows UI. The result is uniformity, repeatability, efficiency, and fewer errors. That’s just what I need.

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Technorati Tags: , , , ,

The PassThru Parameter: Gimme Output

$
0
0

I love to explain things in clear, simple terms. So, a few weeks ago, I tweeted a request for a Windows PowerShell concept that could use more explanation. Andy Fair (@ravensportal) from Oklahoma City asked for a clear explanation of the PassThru parameter. PassThru is frequently used and frequently ignored, so I’m taking him up on his idea. Thanks, Andy!

The PassThru parameter lets you request output from cmdlets that return no output by default. It’s used most often to verify that the cmdlet did what you intended.

 

Using the PassThru Parameter

In Windows PowerShell, Get-* cmdlets almost always return the object that they get. New-* cmdlets typically return the objects that they create. Add-*, Format-*, Convert-*, and Update-* cmdlets sometimes return the objects that they’ve changed. But many cmdlets return nothing – at least by default.

For example, the Copy-Item cmdlet doesn’t return anything. It just copies the object in the path to the specified destination. In this case, it copied the OutputFile.txt file to the CopyTest subdirectory.

    PS C:\> Copy-Item -Path C:\ps-test\OutputFile.txt -Destination .\CopyTest
    PS C:\> 

 

This is fine, because I typically don’t want any noise in my scripts. But, if I want to verify that the file got to its intended destination, I need to run a Get-ChildItem or Test-Path command. Or, I can use the PassThru parameter of Copy-Item. In Copy-Item, the PassThru parameter returns the copied item in its new location.

    PS C:\> Copy-Item -Path .\OutputFile.txt -Destination .\CopyTest\OutputFile.txt –PassThru

    Directory: C:\ps-test\CopyTest

    Mode                LastWriteTime     Length Name
    ---- ------------- ------ ----
    -a---         2/24/2014   8:43 PM         20 OutputFile.txt    

 

In a script, I can save the output to a variable and check it before I execute the next command.

    $file = Copy-Item -Path .\OutputFile.txt -Destination .\CopyTest\OutputFile.txt –PassThru
    if ($file) { …<keep going>… }   
-or-
    if ($file.FullName –like “*CopyTest\OutputFile.txt”) { …<keep going>…. }

 

When using the PassThru parameter of a command, be sure to check the type of the object that it returns. This information should be in the help file for the command, either in the description of the PassThru parameter or the Outputs section of the cmdlet help file.

For example, the help for Copy-Item says:

“When you use the PassThru parameter, Copy-Item returns an object that represents the copied item. Otherwise, this cmdlet does not generate any output.”

It’s important to check before writing your verification code (e.g. if ($file) ), because not all cmdlets return the object that they change. For example, when the cmdlets in the Azure module (Azure Service Management) have a PassThru parameter, they return a Boolean value — $True or $False  — that represents the success ($True) or failure ($False) of the command.

For example, the Get-AzurePublishSettingsFile cmdlet downloads an identity file, but it does not return an object that represents the file or its secure location. By default, the cmdlet returns nothing, but if you use its PassThru parameter, it returns $True or $False.

    $result = Get-AzurePublishSettingsFile
    if ($result) { …<keep going>… }

 

Find the PassThru Parameter

To find cmdlets in your modules that have the PassThru parameter, use a command like this one. It runs a Get-Command command and saves the results in the $c variable. Then it uses the Where-Object cmdlet to find parameters with the name “PassThru.” If it finds a PassThru parameter, it returns the cmdlet that was saved in the $c pipeline variable. (I’m running Windows PowerShell 4.0, but this command should work on 3.0 and later.)

Get-Command -PipelineVariable c | 
    where { $_.ParameterSets.Parameters.Name -eq "PassThru" } | 
        ForEach-Object {$c}

 

On my machine, it returned 48 cmdlets with the following distribution of verbs. It’s really all over the place.

PS C:\>  $pass | Group-Object -Property Verb
Count Name        Group
—– —-        —–
6 Add             {Add-Computer, Add-Content, Add-History, Add-Member…}
2 Clear           {Clear-ItemProperty, Clear-Variable}
1 Compare         {Compare-Object}
2 Copy            {Copy-Item, Copy-ItemProperty}
1 Disable         {Disable-PSBreakpoint}
1 Enable          {Enable-PSBreakpoint}
1 Export          {Export-Alias}
2 Import          {Import-Alias, Import-Module}
2 Invoke          {Invoke-RestMethod, Invoke-WebRequest}
2 Move            {Move-Item, Move-ItemProperty}
3 New             {New-Alias, New-ModuleManifest, New-Variable}
1 Out             {Out-GridView}
1 Pop             {Pop-Location}
1 Push            {Push-Location}
2 Remove          {Remove-Computer, Remove-PSSnapin}
3 Rename          {Rename-Computer, Rename-Item, Rename-ItemProperty}
1 Restart         {Restart-Service}
1 Resume          {Resume-Service}
8 Set             {Set-Alias, Set-Content, Set-Item, Set-ItemProperty…}
1 Show            {Show-Command}
2 Start           {Start-Process, Start-Service} 


 

Implement the PassThru Parameter

You can also add the PassThru parameter to the scripts and functions that you write. Because PassThru doesn’t take a value, its parameter type is always Switch or SwitchParameter, regardless of the type of the object that it returns. And, PassThru parameters should always be optional (not mandatory). For example, I have a little script that adds a timestamp to the file name. By default, it doesn’t return any output, so I always run a Get-Item or Test-Path when it completes. Instead, I’ll add a PassThru parameter. Here’s the “before” version:

    Param
            (
                [parameter(Mandatory=$True)]
                [String]
                $FilePath
            )

    if (!(Test-Path $FilePath)) {throw "Can't find file\folder at $filepath"}

    $item = Get-Item $FilePath
    $timestamp = (Get-Date $item.Lastwritetime -Format o) -replace ":", "-" 

    $newname = $item.BaseName + "-" + $timestamp + $item.Extension
    Rename-Item -Path $FilePath -NewName $newname

 

Now, I’ll add a PassThru parameter. It’s optional and the parameter type is Switch. I also add a line of code at the end that checks the value of that PassThru parameter. Switch parameters have a Boolean ($True/$False) value. The value is $True if the parameter is used (IsPresent) in the command and $False if it is not used.

In my code, if the PassThru parameter is $True ( if ($PassThru) ), I call the Get-Item cmdlet to return the file with the new name.

    Param
    (
        [parameter(Mandatory = $True)]
        [String]
        $FilePath,
        
        [parameter(Mandatory = $False)]
        [Switch]
        $PassThru
    )

    if (!(Test-Path $FilePath)) { throw "Can't find file\folder at $filepath" }

    $item = Get-Item $FilePath
    $timestamp = (Get-Date $item.Lastwritetime -Format o) -replace ":", "-"

    $newname = $item.BaseName + "-" + $timestamp + $item.Extension
    Rename-Item -Path $FilePath -NewName $newname

    if ($PassThru) { Get-Item $newname }

 

But, wait! The Rename-Item cmdlet that I call to change the file name has a PassThru parameter that returns the item that it renamed. All I need to do is to pass the value of my PassThru parameter to the PassThru parameter of Rename-Item.

Switch parameters can take a $True or $False value. Just type a colon (:) after the parameter name and then type the Boolean value. Here’s the syntax:

    -PassThru:$True
    -PassThru:$False

 

Because my PassThru parameter also has a Boolean value, I can type the colon and then type the $PassThru parameter.

So I can replace:

    if ($PassThru) { Get-Item $newname }

… with a Rename-Item command that uses the PassThru parameter:

    Rename-Item -Path $FilePath -NewName $newname -PassThru:$PassThru

This instance of reusing the PassThru parameter of Rename-Item isn’t just a corner case. In almost all of my scripts and functions that have a PassThru parameter, I just reuse the PassThru parameter of the final command.

 

Thanks again for the question, Andy. If you’d like me to explain a Windows PowerShell concept, or something that you’ve seen in a forum or blog post, just let me know.

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.

[Google+] [Twitter] [Facebook] [LinkedIn] [StumbleUpon] [Digg] [Reddit] [del.icio.us] [Google Bookmark]

PowerShell Studio 2014: More Function Builder Updates

$
0
0

Yes, we updated the Function Builder yet again! We just released a new build of PowerShell Studio 2014 (v4.1.72). We wanted to highlight some changes and additions:

 

Function Validation

The Function Builder will now validate your function names by checking if it already exists in your script, thus preventing duplicate functions.

Function Exits

 

Create Functions from selection

If you select a section of script when inserting a new function, the Function Builder will use the selected text as the body of the function. In addition, the function builder will pick potential parameters and present you with the following dialog:

Convert Variables to Parameters

In this dialog you can select the variables you wish to convert into parameters.

 

Parameter Name Refactoring

If you rename a parameter in the Function Builder, PowerShell Studio will now update the parameter references within your script:

Parameter Refactoring

 

Next we will cover some other changes and additions to PowerShell Studio 2014.

[Google+] [Twitter] [Facebook] [LinkedIn] [StumbleUpon] [Digg] [Reddit] [del.icio.us] [Google Bookmark]

PowerShell Studio 2014: Refactoring and Other Updates

$
0
0

In the last PowerShell Studio article, we spoke about the updates to the Function Builder in the v4.1.72 service build. Now we will discuss the other updates in the v4.1.72 build.

 

Improved Rename Refactoring

– We greatly improved the Refactoring speed when renaming variables and parameters.

– Refactoring now takes splatting into account when renaming function parameters and variables.

Rename Refactoring and Splatting

– Refactoring now takes Set-Variable and Clear-Variable cmdlets into account.

Rename Set-Variable

Improved Reference Highlighting

To match the rename refactoring, our Reference Highlighting also takes into account the different ways variables and parameters are references:

Reference Highlighting

You can trigger Reference Highlighting by double clicking on any object in the editor.

 

Block Comment Ribbon Button

We added a Block Comment button to the Ribbon:

Block Comment

Before you could only access the block comment using the following keyboard shortcut: (Ctrl + Shift + Alt + Q)

When you select text, it will place a block comment around the selection:

Block Comment Selection

If there is no selection it will simply insert an empty block comment:

Block Comment no Selection

 

Other Updates

– The Enhanced Console Input will now try to determine the console’s current path in order to display the correct relative path when using the PrimalSense.

– You can now expand parameter aliases for functions using the context menu.

Expand All Aliases command now expands parameter aliases and partial parameter names.

– Corrected Expand All Aliases and Convert all Cmdlets to Aliases keyboard shortcuts.

Expand All Aliases Ctrl + Shift + A
Convert all Cmdlets to Aliases Ctrl + Shift + Alt + A
[Google+] [Twitter] [Facebook] [LinkedIn] [StumbleUpon] [Digg] [Reddit] [del.icio.us] [Google Bookmark]

Delete Desktop Icons: A Windows PowerShell Tip

$
0
0

I’m a rather geeky type person with a passion for clean desktops. My physical desktop is always pretty clean and I like my computer desktops to be clean, too. I don’t like anything on my desktop, including icons, especially on Windows 8.1, Server 2012 R2, and later, with their Start screen and taskbar. (I make an exception for the Recycle Bin, which really isn’t an icon.) But habits die hard and some people like icons, so they’re still popular.

Automating the removal of icons from Windows 7 and earlier versions of Windows is particularly difficult. Most programs add their desktop icons to the All Users or Public desktop. To use Windows PowerShell to delete icons from desktops on Windows 7 and earlier, you need to take ownership of the All Users directory. That hardship seems to have been eliminated on Windows 8.1.

To remove all icons from all Windows 8.1 desktops on a single computer:

  1. Start Windows PowerShell (or PowerShell Studio) with the Run as administrator option.
  2. Run this command:
Remove-Item C:\Users\*\Desktop\*lnk –Force

The Force parameter is required on Windows 8.1 Pro and it doesn’t do any harm on other systems.

To remove all icons from a collection of local and remote computers in a Servers.txt file.

Invoke-Command -ComputerName (Get-Content Servers.txt) `
    -ScriptBlock { Remove-Item C:\Users\*\Desktop\*lnk -Force }

 

If you run this command frequently, create a function for it and add it to your Windows PowerShell profile. I also add a warning that reminds me when I’m not running as an administrator. The cool isAdmin function is one of several versions of this function. This one was developed by Windows PowerShell MVP Shay Levy.

Finally, because I’m deleting something, I add the SupportsShouldProcess parameter of the CmdletBinding attribute and I make that link path a parameter (-Path), in case a user prefers to remove icons in only one user desktop.

 

#PowerShell MVP Shay Levy (@ShayLevy on Twitter)
function isAdmin
{
([
Security.Principal.WindowsPrincipal] `
[
Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(`
[Security.Principal.WindowsBuiltInRole]“Administrator”)
}

 

function Remove-Icons
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param
    (
        [string]
        $Path = "C:\Users\*\Desktop\*lnk"
    )
    if (!(IsAdmin))
    {
        Write-Warning "To remove icons from all desktops, start 
                       Windows PowerShell with the 'Run as administrator' option."
    }
    
    if ($pscmdlet.ShouldProcess($path))
    {
        Remove-Item $Path -Force
    }
}

 

Hope this helps to keep your desktop clean.

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.

[Google+] [Twitter] [Facebook] [LinkedIn] [StumbleUpon] [Digg] [Reddit] [del.icio.us] [Google Bookmark]

PowerShell Studio: Form and Script Recovery

$
0
0

Once in a while our support team gets a request to recover a script from a packaged executable because the original was lost. In fact this just happened recently when a user contacted us to recover his lost form project from his packaged script. We were able to recover the script, but unfortunately the user still had to rebuild all his GUI forms from scratch. He was at least fortunate enough to be able to copy and paste the script portions of his project without having to completely rewrite it all.

Please don’t find yourself in the same situation.

For our public service announcement we present you with these preventative steps to save you from disaster and wasted time:

 

1. Enable Recovery Data

PowerShell Studio has the option to include recovery data in your exported scripts.  This recovery data allows you to reconstruct your original form file or project from the exported script.

image

For more details on the recovery feature please refer to the Form Recovery blog article. This option is enabled by default when you start PowerShell Studio for the first time.

The recovery feature is all well and good, but it will not help your if you hard drive crashes and you never copied the exported script to another location; therefore we go onto step 2.

 

2. Backup your scripts and use Source Control!

I can’t stress this enough: Backup frequently! This is made easier when using versioning tools such as VersionRecall and ChangeVue. These tools will help save your rear and hours of work by allowing you to easily recover files from a repository. If you accidently override a file, you can roll back the file to a known previous working state. You can even restore the file if you accidently deleted it. It is very important to keep your repositories and backups on a separate storage device so that you can recover your scripts when your local hard drive crashes. VersionRecall has a very useful feature where you can specify a secondary storage location, which can be a lifesaver if your primary storage device ever fails.

Redundancy is good and that brings us to the next optional step.

 

3. Use the Cloud

Another option is to utilize cloud services such as Dropbox or OneDrive as an additional backup method. This may be a viable option if your scripts are not sensitive and your company’s policies permit it. Cloud services can also be used in conjunction with versioning tools such as VersionRecall to add an additional layer of protection. It is important to note that some cloud services only store previous versions of a file for a short time; therefore it may not be the best tool for keeping track of versions. Some services do offer special packages that allow you retain the previous versions during the life of your subscription.

 

I know there are some people out there that don’t believe they need to version or backup their scripts, but the day their hard drive crashes and they have an impending deadline, they will wish they did. So if you follow these steps, you can sleep better at night knowing that your scripts are safe and you have a recovery strategy.

[Google+] [Twitter] [Facebook] [LinkedIn] [StumbleUpon] [Digg] [Reddit] [del.icio.us] [Google Bookmark]

A Better ToString() Method for Hash Tables

$
0
0

I didn’t make it to the PowerShell Summit 2014 in Europe this year, but I’ve been getting much of the benefit by watching the Summit presentations on YouTube. After watching Windows PowerShell MVP Bartek Bielawski’s excellent presentation, in which he reviews PSCustomObjects and dynamic type data, I was inspired to fix a shortcoming in the .NET framework that often frustrates me.

It’s easy to create a hash table in Windows PowerShell:

@{Name = "Fred"; ID = "3"}

 

And, beginning in Windows PowerShell 3.0, it’s easy to get a hash table string from a file and convert it to a hash table object. Use the Invoke-Expression cmdlet and the Raw parameter of the Get-Content cmdlet. (Some nice folks on Twitter have reported that this technique does not work on files with digital signatures.)

$result = Invoke-Expression (Get-Content –Raw –Path <filename>)

 

For example, the module manifest files in Windows PowerShell contain a hash table string. If you open them in Notepad, they look like this:

image

If you get the content of that hash table…

Get-Content (Get-Module –List BranchCache).Path

… you get a string.

But if you use Invoke-Expression and the Raw parameter of Get-Content, you get a hash table. And, you can get the values of keys in the manifest hash table, such as FormatsToProcess, either by using a dot to get the property or by using conventional hash table syntax.

PS C:\> $manifest = `
    Invoke-Expression (Get-Content –Raw -Path (Get-Module –List BranchCache).Path)
PS C:\> $manifest.FormatsToProcess
BranchCache.format.ps1xml
PS C:\> $manifest['FormatsToProcess']
BranchCache.format.ps1xml

 

So, the only piece that was missing was saving a hash table as a string in a file. The obvious solution is the ToString() method of hash tables, but that just returns the type.

PS C:\> $manifest = `
    Invoke-Expression (Get-Content –Raw -Path (Get-Module –List BranchCache).Path)
PS C:\> $manifest.toString()
System.Collections.Hashtable

 

I wrote a little function that converts any hash table to a string. There’s no magic here. It just builds a string that conforms to the hash table syntax for Windows PowerShell. The only tricky part was using escape characters to preserve quotation marks when the key includes a special character. (Hint: I used PowerRegEx in the SAPIEN Productivity Pack to find that “\s”.)

    function Convert-HashToString
    {
        param
        (
            [Parameter(Mandatory = $true)]
            [System.Collections.Hashtable]
            $Hash
        )
        $hashstr = "@{"
        $keys = $Hash.keys
        foreach ($key in $keys)
        {
            $v = $Hash[$key]
            if ($key -match "\s")
            {
                $hashstr += "`"$key`"" + "=" + "`"$v`"" + ";"
            }
            else
            {
                $hashstr += $key + "=" + "`"$v`"" + ";"
            }
        }
        $hashstr += "}"
        return $hashstr
    }

 

But, I don’t want to run a function or script. I want this code to run when I call the ToString() method of a hash table. So, I used the Update-TypeData trick that Bartek explains so nicely. (You can also read about it in the help topic for Update-TypeData, but Bartek is more fun to watch.)

This was easier than I thought. I used the Update-TypeData cmdlet. I set the value of the TypeName parameter to the full name of the hash table type, System.Collections.HashTable. It’s a script for a method, so I used the ScriptMethod member type. And, I want to override the current (not very helpful) ToString() method, so I set the MemberName parameter value to ToString.

The Value is the content of my little function. The only change I made was to replace the $hash parameter value with $this, which indicates the instances of the HashTable class.

    Update-TypeData -TypeName System.Collections.HashTable `
    -MemberType ScriptMethod `
    -MemberName ToString `
    -Value { $hashstr = "@{"; $keys = $this.keys; foreach ($key in $keys) { $v = $this[$key]; 
             if ($key -match "\s") { $hashstr += "`"$key`"" + "=" + "`"$v`"" + ";" }
             else { $hashstr += $key + "=" + "`"$v`"" + ";" } }; $hashstr += "}";
             return $hashstr }

 

Voila! When I ran it, I could use the new method to generate a hash table string.

    PS C:\> $h = @{ Name = "JuneB"; Language = "PowerShell" }

    PS C:\> $h.Language
    PowerShell

    PS C:\> $h.ToString()
    @{ Name = "JuneB"; Language = "PowerShell"; }

 

Now, it’s easy to save the hash table string in a file.

    PS C:\> $h.ToString() > .\hash.txt

    PS C:\> Get-Content .\hash.txt
    @{ Name = "JuneB"; Language = "PowerShell"; }

 

And, convert it back to a hash table.

PS C:\> $hashback = Invoke-Expression (Get-Content -Raw .\hash.txt)

PS C:\> $hashback.language 
PowerShell
PS C:\> $hashback.gettype().fullname
System.Collections.Hashtable

 

The only glitch is that dynamic type data is specific to a session, so I saved the Update-TypeData command in my Windows PowerShell profile. Now, it’s available to me in every Windows PowerShell session that uses my profile.

The moral of the story is that it takes an hour to watch a PowerShell Summit presentation, but the inspiration might save you days of work.

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com and follow her on Twitter at @juneb_get_help.

[Google+] [Twitter] [Facebook] [LinkedIn] [StumbleUpon] [Digg] [Reddit] [del.icio.us] [Google Bookmark]

Expand All Aliases: Ctrl+Shift+A

$
0
0

Windows PowerShell aliases are very handy. They typically have fewer characters than the commands and parameters that they represent, so they’re quicker to type and great for tweeting. However, it has long been an established best practice that aliases should not be used in blogs, in help, in examples, or in any shared scripts and commands. They create an extra layer of confusion for beginners and they’re an obstacle for anyone who needs to maintain the script.

Still, we use them. I know that, despite my best efforts, I type “dir” and “where” and “gcm” and “ipmo” and “sls”  and, worst of all, “foreach” when I mean the ForEach-Object cmdlet, not the ForEach statement.

Fortunately, some nifty features in PowerShell Studio 2014 make it easy to conform to the best practice, despite our habits.

  • When you type an alias, PrimalSense shows that it’s a alias.

image

  • When you type a cmdlet alias or parameter alias and then press TAB, PowerShell Studio expands it. (You can disable this feature in Options\Editor\Enable alias tab expansion.)
  • When you type a partial parameter name (such as –m or –mod for –Module) and a space, PowerShell Studio expands it.
  • To expand an alias, right-click and click Expand to Cmdlet or Expand Parameter.

 

But, when I’m scripting, I’m a bit too focused to think about aliases. Instead, when I’m done with my script, just before I sign it, I type:

CTRL + SHIFT + A

Ctrl+Shift+A converts all cmdlet and parameter aliases, and parameter partial names*, to their full names with correct capitalization. What an easy way to support a best practice.

Oh, and in case you’re wondering, PowerShell Studio 2014 is happy to undo all of your good deeds. You can right-click and click Convert to Alias or type Ctrl+Shift+Alt+A to convert all cmdlet and parameter names to aliases.

 

A few notes * :

  • Beginning in PowerShell Studio 2014 version 4.1.72, Ctrl+Shift+A expands aliases and Ctrl+Shift+Alt+A contracts names to their aliases. In earlier versions, Ctrl+Shift+Alt+A expands aliases and Ctrl+Shift+A contracts them.
  • Also, beginning in version 4.1.72,  Ctrl+Shift+A expands cmdlet aliases, parameter aliases and partial parameter names. In earlier versions, Ctrl+Shift+Alt+A expands only cmdlet aliases.
  • The alias expansion features don’t expand aliases that you have defined in your profile. In best practice fashion, just like your users, the PowerShell Studio Editor window does not have access to your profile.
  • The alias expansion features don’t recognize aliases that you have defined in your script. (You really shouldn’t define aliases in a script anyway.)

 

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.

[Google+] [Twitter] [Facebook] [LinkedIn] [StumbleUpon] [Digg] [Reddit] [del.icio.us] [Google Bookmark]

Saving Passwords for Add-AzureAccount

$
0
0


One of the great features of the recent versions of Azure PowerShell is a non-interactive option for the Add-AzureAccount cmdlet. Unfortunately, the instructions tell you to save your Azure password in plain text, but there are much more secure alternatives. I explain one in this post.

——————-

As users of Azure PowerShell know well, there have always been two distinct ways of making your Azure account available to Windows PowerShell.

You can download (Get-AzurePublishSettingsFile) and import (Import-AzurePublishSettingsFile) a PublishSettings file. This technique uses a management certificate with security credentials and, while it’s a bit more complex, once the certificate is on the machine, you can access your Azure account in Windows PowerShell.

You can also use Azure Active Directory (Azure AD). The Add-AzureAccount cmdlet prompts you to sign into your Azure account by popping up a sign-in window. When sign-in succeeds, information about your Azure account is saved in a subscription data file in your roaming user profile and Windows PowerShell gets an access token that it can use to access your Azure account on your behalf. This is a great strategy, except that the token expires (in about 12 hours) and the interactive sign-in prompt prevents you from using it in a script.

Until now.

The newest versions of Azure PowerShell (I have 0.8.9) let you use the Credential parameter of Add-AzureAccount to suppress the sign-in pop-up. It works only with an organizational ID (not with a Microsoft account), but it’s easy to create an organizational ID for any account. More on that below).

Here’s the snippet for adding an Azure account to Windows PowerShell in a script. As an alternative to the equally interactive Get-Credential cmdlet, you use the New-Object cmdlet to create a PSCredential object. Then, you pass it the username and a secure string form of the password of your Azure account. When you call Add-AzureAccount with the PSCredential object, it uses the credentials to sign you in so you are not prompted.

    $cred = New-Object System.Management.Automation.PSCredential($userName, $securePassword)
    Add-AzureAccount -Credential $cred

 

This is a great solution, but the instructions in How to install and configure Azure PowerShell tell you to use plain text for both the user name and password.

    $userName = "<your organizational account user name>"
    $securePassword = ConvertTo-SecureString `
        -String "<your organizational account password>" -AsPlainText -Force
    $cred = New-Object System.Management.Automation.PSCredential($userName, $securePassword)
    Add-AzureAccount -Credential $cred

 

This is neither secure nor necessary. Although no method is completely secure, storing a password in plain text in a text/script file on disk is downright scary. If you need to save a password on disk, at least save it in an encrypted string. It’s easy to do. In this code, we use Read-Host to prompt you for the password (just once). The AsSecureString parameter is equivalent to piping the password string that you type to the ConvertTo-SecureString cmdlet. The result is a secure string.

    $secure = Read-Host -AsSecureString "Enter your Azure organization ID password."

 

Use the ConvertFrom-SecureString cmdlet to convert the secure string to an encrypted string and then, to save it to disk, redirect the encrypted string to a file or pipe it to the Set-Content cmdlet.

    ConvertFrom-SecureString -SecureString $secure > $FilePath
-or-
    ConvertFrom-SecureString -SecureString $secure | Set-Content -Path $FilePath

If anyone happened to peek in that file, they’d see something that looks like this.

01000000d08c9ddf0115d1118c7a00c04fc297eb010000009b2299cb1e643949ae2af8e01153829c00000
00001000000d08c9ddf0115d1118c7a00c04fc297eb010000009b2299cb1e643949ae2af8e01153829c00
000000020000000000106600000001000020000000e5e552e0bc6def08bf715a2511ad26cef2a41211f28
35cba161944b4f17fc017000000000e80000000020000200000008b267e1ac697b27b381925dc4cc43db6
a06c6f5524442e340e7b456fb9005a42200000007c829e84ebd387a803b10ca08d0c43c0df7abbc2e1067
07ad21178d0b1f215084000000093ae66ef19b086d52da13b76308ee7f4910d7b70c5af1c8366db5f8be1
c746c301eb88c11e4609ad4ef90a638adba7c571b739978952c165626075282e58eec6

Then, in Azure PowerShell scripts, you use almost the same code for your Add-AzureAccount command, except that you add a Get-Content command to get the encrypted string from its file.

    $userName = "coadmin@myenterprise.onmicrosoft.com"
    $securePassword = ConvertTo-SecureString (Get-Content -Path $FilePath)
    $cred = New-Object System.Management.Automation.PSCredential($userName, $securePassword)
    $result = Add-AzureAccount -Credential $cred

This is easy and much more secure. Here’s a little function that saves any password to disk as an encrypted string.

    function Save-Password
    {
        Param
        (
            [parameter(Mandatory = $true)]
            [String]
            $FilePath,
            
            [parameter(Mandatory = $true)]
            [Switch]
            $PassThru
        )
        
        $secure = Read-Host -AsSecureString "Enter your Azure organization ID password."
        $encrypted = ConvertFrom-SecureString -SecureString $secure
        $result = Set-Content -Path $FilePath -Value $encrypted -PassThru
        
        if (!$result)
        {
            throw "Failed to store encrypted string at $FilePath."
        }
        if ($PassThru)
        {
            dir $FilePath
        }
    }

 

Now, about that organizational ID. You can create at least one organizational ID for any Azure account, even one that is secured with a Microsoft account, such as an Outlook.com account. You can find the instructions in the “Use the Azure AD method” section of How to install and configure Azure PowerShell.

I still haven’t figured out a workaround for the expiring Azure AD token, so I include an Add-AzureAccount command that gets a new token in every script. But the password is saved as an encrypted string and the user of the script is not interrupted.

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.

[Google+] [Twitter] [Facebook] [LinkedIn] [StumbleUpon] [Digg] [Reddit] [del.icio.us] [Google Bookmark]

Get-Duplicates (or Unique items)

$
0
0

I take a lot of free online coding classes, mainly from Coursera and Udacity, and I’ve picked up a lot of programming tricks in other languages that are easy translated to Windows PowerShell.

In a Java class on Udacity, I learned a cool way to find duplicates in any collection. It uses the fact that the keys in hash tables must be unique. The parser throw an “Item has already been added” error if you try to add a key that’s already in the hash table.

In this example, I try to add “Day” to a hash table that already has an “Day” key. The value is arbitrary.

 

    $hash = @{ Day = "Wednesday"; Weather = "Sunny" }
    $hash.Add("Day", "Friday")

 

    ERROR: Exception calling "Add" with "2" argument(s): "Item has already been added. Key in dictionary: 'Day'  Key being added: 'Day'"
    Test.ps1 (15): ERROR: At Line: 15 char: 1
    ERROR: + $hash.Add("Day", "Friday")
    ERROR: + ~~~~~~~~~~~~~~~~~~~~~~~~~~
    ERROR: + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    ERROR: + FullyQualifiedErrorId : ArgumentException
    ERROR:

 

To detect a duplicate in a small collection, create a hash table and add the items to the hash table as keys.

    $hash = @{ }
    "a", "b", "a", "c", "d" | ForEach { $hash.Add($_, 0) }

 

Use a Try block to add each item as a key with a value of 0. If a MethodInvocationException occurs in the Try block code, instead of erroring out and interrupting the script, it falls in the Catch block. I use the Catch block to save the duplicates in an array.

    $Items = "a", "b", "a", "c", "d"
    $hash = @{ }
    $duplicates = @()

 

        foreach ($item in $Items)
        {
            try
            {
                $hash.add($item, 0)
            }
            catch [System.Management.Automation.MethodInvocationException]
            {
                $duplicates += $item
            }
        }

 

You can return the duplicates that you saved and/or the unique items, which are the keys in the hash table.

    $hash.keys
    c
    a
    d
    b

 

For the final version of my little script, I convert the hash table to an ordered dictionary, which preserves the order in which the keys were added. I also allow users to pipe the items to the script by adding the ValueFromPipeline parameter attribute and the Process block that supports it.

<#
    .SYNOPSIS
        Gets duplicates or unique values in a collection.

    .DESCRIPTION
        The Get-Duplicates.ps1 script takes a collection and returns
        the duplicates (by default) or unique members (use the Unique
        switch parameter).

    .PARAMETER  Items
        Enter a collection of items. You can also pipe the items to
        Get-Duplicates.ps1.

    .PARAMETER  Unique
        Returns unique items instead of duplicates. By default, Get-Duplicates.ps1
        returns only duplicates.

    .EXAMPLE
        PS C:\> .\Get-Duplicates.ps1 -Items 1,2,3,2,4
        2

    .EXAMPLE
        PS C:\> 1,2,3,2,4 | .\Get-Duplicates.ps1
        2

    .EXAMPLE
        PS C:\> .\Get-Duplicates.ps1 -Items 1,2,3,2,4 -Unique
        1
        2
        3
        4

    .INPUTS
        System.Object[]

    .OUTPUTS
        System.Object[]

    .NOTES
    ===========================================================================
     Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2014 v4.1.72
     Created on:       10/15/2014 9:34 AM
     Created by:       June Blender (juneb)
#>

param
(
    [Parameter(Mandatory = $true,
               ValueFromPipeline = $true)]
    [Object[]]
    $Items,
    
    [Parameter(Mandatory = $false)]
    [Switch]
    $Unique
)
Begin
{
    $hash = [ordered]@{ }
    $duplicates = @()
}
Process
{
    foreach ($item in $Items)
    {
        try
        {
            $hash.add($item, 0)
        }
        catch [System.Management.Automation.MethodInvocationException]
        {
            $duplicates += $item
        }
    }
}
End
{
    if ($unique)
    {
        return $hash.keys
        
    }
    elseif ($duplicates)
    {
        return $duplicates
    }
}

 

Remember that qualification about small collections of items? This strategy is a little programming trick that is not optimized for large data sets. For those, stick with Microsoft.PowerShell.Utility\Get-Unique and other optimized methods.

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.

[Google+] [Twitter] [Facebook] [LinkedIn] [StumbleUpon] [Digg] [Reddit] [Google Bookmark]

Throwback Thursday: Making batch files into executables

$
0
0

PrimalScript 2014 can make a variety of script files into executable files. We call that packaging. This feature is mostly used by VBScript and PowerShell administrators to wrap their scripts into an executable file that can be shipped to end users and prevent prying eyes from seeing what makes the magic happen. For the aforementioned VBScript and PowerShell scripts this usually means a package that executes the code in memory, without the use of a temporary file.

We did however receive quite a number of requests lately to also package Batch or CMD files. Here is where the “Throwback Thursday” comes in. We didn’t think too many admins still use batch files. We are however always happy to comply with simple requests and PrimalScript can now package these batch files into executable files.

SNAGHTML2bffac0

Select the “CMD x64” or “CMD x86” engine and you are all set. Obviously there is only a command line engine for batch files.

Since there is no actual batch script engine, these file cannot be executed in memory. A temporary file is created and executed. Please note that user may have the ability to peek at the temporary files while your batch job is executing. Because we cannot determine in advance where the temporary folder will be, your current directory is not necessarily where the executable file resides. Please make sure to always set the current directory your process requires.

Also of note is that the cmd scripts are stored as unicode in the package and are converted to ANSI before written to the temporary file. The conversion uses the default codepage on the system executing the package.

[Google+] [Twitter] [Facebook] [LinkedIn] [StumbleUpon] [Digg] [Reddit] [Google Bookmark]

Update-OneGet: Install OneGet on PS 3.0+

$
0
0

One of the coolest new features in the Windows PowerShell 5.0 preview is the OneGet module. Windows PowerShell program manager, Dan Harman, describes OneGet as a “package manager manager,” but for most of us, it represents a simple, repeatable, and reliable way to install, update, and uninstall a variety of available programs from a variety of sources.

For example, if you want to install MarkDownPad2, a very simple and highly regarded HTML mark-up (or down) editor, you run a command like this:

Find-Package MarkDown2 | Install-Package

Yes. It’s just that easy. Really. And, if you need to uninstall it.

Get-Package MarkDown2 | Uninstall-Package

There are two versions of OneGet floating around:

 

The official version of OneGet is now available only in the Windows PowerShell 5.0 preview, which supports Windows 8.1, Windows Server 2012 R2, and later. I don’t know if they plan to make the official version available for earlier versions of Windows or Windows PowerShell.

But, according to Garrett, the experimental version is designed to work on Windows PowerShell 3.0 and later, which supports Windows 7 SP1, Windows Server 2008 SP2, Windows Server 2008 R2 SP1, and later.

So, I wrote a little script, Update-OneGet.ps1 (attached below) that downloads the latest experimental version of the OneGet module, unblocks it (so you can run it with the RemoteSigned execution policy), unzips it, and saves it in a directory that you specify.

The Warnings

Now, the predictable warnings. Caveat emptor, especially when the download is free.

  • You shouldn’t run any unofficial software on a production system or on any system with sensitive data or guaranteed up-time.
  • Before running any open-source code on any system, review the code and make sure that it’s safe, or consult people who can verify that it’s safe.
  • By design, open-source code is a work in progress. The version that you download this morning might work a bit differently than the version that you download in the afternoon.

And one less predictable one:

  • Dan Harman mentioned in his talk that the official Uninstall-Package cmdlet might not completely uninstall a program. You might need to run an MSI to completely uninstall it.

Get the Experimental OneGet

I wrote a little script that downloads, unblocks, unzips, and installs the experimental version of the OneGet module on your system. It’s designed to be run repeatedly (hence the Update verb), so it always deletes everything in the directory and installs a new copy.

You can run it on Windows PowerShell 3.0 and later and you don’t need to have a GitHub user account. (If you have a GitHub account, you don’t need to log in, because the script doesn’t use it.)

The script has a Path parameter that takes the path to a directory. Update-OneGet installs the module in that directory.

Caution: Do not install the experimental version of OneGet in $pshome\Modules. If you do, it might prevent you from installing, uninstalling or updating the official version of the module.

To be safe, especially on systems that have both the official and experimental versions of the OneGet module, install the experimental version in a directory that will not be auto-loaded; that is, a directory that is not listed in your $env:PSModulepath variable. For example:

$home\Documents\Test\OneGetExperimental

-or-

C:\ps-test\OneGetExperimental

If you do not and will not have the official version, and you want the module to import automatically, you can provide the path to your current-user module directory, such as your $home\Documents\WindowsPowerShell\Modules\OneGetExperimental directory.

In case you’re wondering, the Path value doesn’t affect the module name. The module name is actually determined by the name of the module manifest file, so both the official and experimental versions are “OneGet” in Windows PowerShell.

The script also has an Import switch parameter that imports the module into the session after it’s installed. It’s optional, of course.

To get help for the script, use Get-Help with the path and file name of the script. For example:

Get-Help .\Update-OneGet.ps1

Importing OneGet (the experimental version)

After you install the OneGet module, you need to import it.

If you installed OneGet in your current-user modules directory, or any directory that is listed in $env:PSModulePath, Windows PowerShell imports it automatically when you use a cmdlet in the module, such as Find-Package, or use Get-Command or Get-Help on a module cmdlet without wildcards.

If you installed the module in a different directory, you can import it by using the path to the OneGet module manifest. Here’s how:

Import-Module <Path>\OneGet.psd1

If you are running Windows PowerShell 5.0 preview, you’ll have both the official and experimental versions of the module. When you have commands in your session with the same name, the commands that run are the ones added to the session last (most recently) For details, see about_Command_Precedence. And, because the modules have the same name, qualifying the cmdlet name with the module name (<Module>\Cmdlet) doesn’t help.

To avoid this confusion, when you want to use a command in the experimental version of the OneGet module, explicitly remove any accidentally auto-loaded versions of OneGet from the session and import the version that you want.

    function Import-OneGetEx
    {        
        if (Get-Module OneGet) { Remove-Module OneGet }        
        Import-Module <Path>\OneGet.psd1
    }

 

About OneGet

Today, there is no official help for the cmdlets in the OneGet module, but the Microsoft writers are working hard on it, and it should be available soon, both for the official and experimental modules.

In the meantime, I recommend Dan Harman’s terrific talk at the PowerShell Summit 2014 in Europe: Dan Harman – PowerShell Repositories Unleashed. In this talk, Dan introduces OneGet and its favorite child, PowerShellGet, and then delves into how to create a package provider that connects the OneGet framework to a particular package manager repositories.

 

Script: Update-OneGet.ps1

With special thanks to Windows PowerShell MVP Doug Finke (@dfinke) for reviewing the code.

You can copy the Update-OneGet.ps1 script code here or download a ZIP file of the Update-OneGet.ps1 script from the SAPIEN Downloads site.

To download, sign in, in the left nav, click Sample Scripts, click Update-OneGet.ps1.zip, and in the top right corner, click Download.

 

<#
    .NOTES
    ===========================================================================
     Created with:     PowerShell Studio 2014, Version 4.1.74
     Created on:       11/10/2014 4:06 PM     
     Company:          SAPIEN Technologies, Inc.
     Contact:          June Blender, juneb@sapien.com, @juneb_get_help
     Filename:         Update-OneGet.ps1
    ===========================================================================

    .SYNOPSIS
        Downloads, installs, and imports the latest OneGet module from GitHub.

    .DESCRIPTION
        Update-OneGet.ps1 downloads, installs, and (optionally) imports the
        latest experimental version of the OneGet module from http://OneGet.org,
        which is linked to the OneGet project in GitHub.

        The script downloads the OneGet.zip file from OneGet.org and saves it in
        your downloads folder, $home\Downloads. It unblocks the file, unzips it,
        and installs it in the directory specified by the Path parameter.

        If you include the optional Import parameter, the script imports the module
        into the current session.

        After running the script, to import the experimental version of OneGet into 
        any session, use the following command:
            Import-Module <path>\OneGet.psd1

        This script runs on Windows PowerShell 3.0 and later. 
    .PARAMETER  Path
        Specifies a directory where the script installs the module. This parameter is
        required. Enter the path to a directory. Do not include a .zip file name extension. 

        If the directory does not exist, the script creates it. If the directory exists, 
        the script deletes the directory contents before installing the module. This lets 
        you reuse the directory when updating.

        To prevent errors, specify a subdirectory of your Documents directory or a test 
        directory.

        CAUTION: Do not specify a path in $pshome\Modules. Installing the experimental build
                 of OneGet in this directory might prevent you from installing, uninstalling, 
                 or updating the official OneGet module from Microsoft.

    .PARAMETER  Import
        Imports the module into the current session after installing it. 

    .EXAMPLE
        .\Update-OneGet.ps1 -Path $home\Documents\Test\OneGet
        
        This command installs the newest OneGet module in the $home\Documents\Test\OneGet
        directory. To import it: Import-Module $home\Documents\Test\OneGet.psd1

    .EXAMPLE
        .\Update-OneGet.ps1 -Path $home\Documents\Test\OneGet -Import
        
        This command installs the newest OneGet module in the 
        $home\Documents\Test\OneGet    directory and imports it into the
        current session. 

    .EXAMPLE
        .\Update-OneGet.ps1 -Path $home\Documents\WindowsPowerShellModules\OneGet

        This command installs the newest OneGet module in the your current-user 
        Modules    directory. Windows PowerShell imports it automatically when you use a 
        command in the module, such as Find-Package.


    .OUTPUTS
        System.Management.Automation.PSCustomObject, System.Management.Automation.PSModuleInfo
        If you use the Import parameter, Update-OneGet returns a module object. Otherwise, it
        returns a custom object with the Path and LastWriteTime of the OneGet module manifest,
        OneGet.psd1
#>

#Requires -Version 3

Param
(
    [Parameter(Mandatory = $true)]
    [ValidateScript({$_ -notlike "*.zip" -and $_ -notlike "$pshome*" -and $_ -notlike "*System32*"})]
    [System.String]
    $Path,

    [Parameter(Mandatory=$false)]
    [Switch]
    $Import
)

#***************************#
#  Helper Functions         #
#***************************#

# Use this function on systems that do not have
# the Extract-Archive cmdlet (PS 5.0)
#
function Unzip-OneGetZip
{
    $shell = New-Object -ComObject shell.application
    $zip = $shell.NameSpace("$home\Downloads\OneGet.zip")
    foreach ($item in $zip.items())
    {
        $shell.Namespace($script:Path).Copyhere($item)
    }
}

#***************************#
#         Main              #
#***************************#
# Remove current OneGet from session
if (Get-Module -Name OneGet) {Remove-Module OneGet}

# Create the $Path path
if (!(Test-Path $Path))
{
    try
    {
        mkdir $Path | Out-Null
    }
    catch
    {
        throw "Did not find and cannot create the $Path directory."
    }
}
else
{
    dir $Path | Remove-Item -Recurse
}

#Download the Zip file to $home\Downloads
try
{
    Invoke-WebRequest -Uri http://oneget.org/oneget.zip -OutFile $home\Downloads\OneGet.zip
}
catch
{
    throw "Cannot download OneGet zip file from http://oneget.org"
}
if (!($zip = Get-Item -Path $home\Downloads\OneGet.zip)) 
{
    throw "Cannot find OneGet zip file in $home\Downloads"
}
else
{
    $zip | Unblock-File
    if (Get-Command Expand-Archive -ErrorAction SilentlyContinue)
    {
        $zip | Expand-Archive -DestinationPath $Path
    }
    else
    {
        Unzip-OneGetZip        
    }    
    
    if (!(Test-Path $Path\OneGet.psd1))
    {
        throw "Cannot find OneGet.psd1 in $Path"
    }

    if ($Import)
    {        
        Import-Module $Path\OneGet.psd1
        if ((Get-Module OneGet).ModuleBase -ne $Path)
        {
            throw "Failed to import the new OneGet module from $Path."
        }
        else
        {
            Get-Module OneGet
        }

    }        
    else
    {
        [PSCustomObject]@{Path = "$Path\OneGet.psd1"; Date = (dir $Path\OneGet.psd1).LastWriteTime}
    }
}

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.

[Google+] [Twitter] [Facebook] [LinkedIn] [StumbleUpon] [Digg] [Reddit] [Google Bookmark]

Removing Objects from Arrays in PowerShell

$
0
0

 

A member of the PowerShell group on Facebook asked how to delete an object from an array. It’s a simple question, but the answer isn’t very simple at all. It’s one of those beginner questions that has an advanced answer.

When I first answered this question in about_Arrays, I didn’t really know much about the subject and I used the (very good) answer that the team gave me. But that answer was incomplete, and while there are many good posts on the topic, none really cover the whole scope of pitfalls.

[Be sure to start with about_Arrays. I’ll assume that you’ve read it and won’t repeat the basics of arrays that are described there.]

 

Why is removing difficult?

Removing objects from arrays should be simple, but the default collection type in Windows PowerShell, an object array (System.Object[]), has a fixed size. You can change objects in the array, but you can’t add or delete them, because that would change the size of the array.

Let’s look at the problem. I’ll create an array called $letters and save a few letters in the array.

 $letters = "a", "b", "c", "d"

 

Now, I’ll try to remove the “c” from the array.

    $letters = $letters - "c"

Method invocation failed because [System.Object[]] does not contain a method named ‘op_Subtraction’.
At line:1 char:2
+  $letters = $letters – “c”
+  ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidOperation: (op_Subtraction:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound

That didn’t work, because object arrays [System.Object[]) don’t have a subtraction (op_Subtraction) method. Let’s find the methods that System.Object[] supports.

Typically, to get the type, methods, and properties of an object, you pipe it to the Get-Member cmdlet. But, when you pipe a collection of objects to Get-Member, Get-Member gets the members of objects in the collection. To get the members of the collection (not the objects that it contains), use its InputObject parameter.

 

image

This command reveals that object arrays, like the one in the $letters variable have a Remove method. Let’s try the Remove method.

image

Remove fails, because object arrays are a fixed size.

This is where beginners get stuck and even experienced users hit Google, Facebook, or a PowerShell forum for help.

Create a new array

One way to remove an object from a fixed-size array is to create a new array that includes only selected objects from the original array. In this command, we use a ForEach loop go through every letter in the $letters array. We add all of the objects in $letters to the $newLetters array, except for “c”. To add objects to the new array, use the + or += operators; the statements are equivalent.

    $newLetters = @()
    foreach ($letter in $letters)
    {
        if ($letter -ne "c")
        {
            #$newLetters = $newLetters + $letter
            $newLetters = $newLetters += $letter
        }
    }

PS C:\> $newLetters
a
b
d

 

Interestingly, when we use the + or += operators, Windows PowerShell actually creates a new array each time in the background, because you can’t change the size of array. Sadly, there is no –= operator. Also, in this very simple example, it’s easier use the Where-Object cmdlet to filter out the “c”, but we can assume that you’re using an array for a more complex task.

$newLetters = $letters | Where-Object { $_ne "c" }

 

You can also use array index notation to exclude items from the new array. In this command, we add the items at indexes 0, 1, and 3 from $letters to the $newLetters array, but we exclude the item at index 2.

    $newLetters = $letters[0, 1, 3]

 

You can reuse the $letters variable name, but you are still creating a new $letters array that is independent of the original one.

    $letters = $letters[0, 1, 3]

 

 

 

 

Use an ArrayList

My favorite solution to the “collection was of a fixed size” problem is to create an ArrayList (System.Collections.ArrayList), instead of the default object array (System.Object[]). ArrayLists behave like arrays, but unlike arrays, ArrayList objects don’t have a fixed size.

To create an array list, cast the variable:

    [System.Collections.ArrayList]$caps = "A", "B", "C", "D"

 

Or, cast the objects. (Don’t forget the parentheses.)

    $caps = [System.Collections.ArrayList]("A", "B", "C", "D")

 

Use the InputObject parameter of Get-Member to get the properties and methods of an array list. There’s a Remove method of ArrayList objects and this one works.

image

 

 

Remove objects from an array list

As the help shows, the Remove method of an ArrayList removes the first instance of the item from the array list. It returns Void, which means nothing. The Remove method changes the original array list. It doesn’t return a new array list.

image

PS C:\ps-test> $caps
A
B
D

 

To remove multiple instances of an object, use a While loop.

    [System.Collections.ArrayList]$caps = "A", "B", "C", "D", "C", "E"
    while ($caps -contains "C") {
        $caps.Remove("C")
    }

You can also use a ForEach loop on an array list, but you can’t remove objects from the array list that you’re traversing with ForEach. In the background, ForEach uses positional indexes and the position of each object in an array list (or any collection) changes when you remove an object.

In the following example, the code uses a ForEach loop to remove the svchost processes from an array list of processes.

    [System.Collections.ArrayList]$p = Get-Process
    foreach ($process in $p)
    {
        if ($process.Name -eq "svchost")
        {
            $p.Remove($process)
        }
    }

Collection was modified; enumeration operation may not execute.
At line:1 char:10
+ foreach ($process in $p)
+ ~~~~
+ CategoryInfo          : OperationStopped: (:) [], InvalidOperationException
+ FullyQualifiedErrorId : System.InvalidOperationException

 

Instead, create an array of svchost processes ($s) and use a ForEach loop to remove each svchost process from $p. Because ForEach is traversing the $s array, you can use it to remove objects from $p.

    [System.Collections.ArrayList]$p = Get-Process
    $s = Get-Process | where ProcessName -eq "svchost"
    foreach ($svchost in $s)
    {
        $p.Remove($svchost)
    }

 

RemoveAt: Remove Objects by Index

You can also use the RemoveAt method of ArrayLists to remove objects. RemoveAt takes the index of the object that you want to remove. In this example, we’ll use a value of 2, which is the index of “C”.

image

PS C:\ps-test> $caps
A
B
D
E

 

It was easy to find the index of “C” in such a small array list, but it you have a larger array list, you can use the IndexOf method to find the index and then use the RemoveAt method to remove the object at that index. You can call the methods in two separate commands or in a single command.

 

    $index = $caps.IndexOf("C")
    $caps.RemoveAt($index)

-or-

    $caps.RemoveAt($caps.IndexOf("C"))

To use the RemoveAt method to remove multiple objects, use a For loop. The For loop is well suited to this task because it use indexes.

But, be careful! When you remove an object from a collection, you change the indexes of every subsequent object in the collection. For example, in the following array list, we want to remove the “C” objects at indexes 2, 3, and 4. But after we remove the “C” object at index 2, the index of the second “C” object changes from 3 to 2. If we advance the index from 2 to 3, we’ll miss the “C” that is now at index 2.

image

 

To compensate, whenever you remove an object, decrement the index by 1. You can use any notation to reduce it’s value by one:  $i = $i – 1, $i –= 1, or $i–.

    [System.Collections.ArrayList]$caps = "A", "B", "C", "D", "C", "E"
    for ($i = 0; $i -lt $caps.count; $i++)
    {
        if ($caps[$i] -eq "C")
        {
            $caps.RemoveAt($i)
            $i--
        }
    }

 

Here’s the same technique used to remove the svchost processes from the $p array list.

    [System.Collections.ArrayList]$p = Get-Process
    for ($i = 0; $i -lt $p.Count; $i++)
    {
        if ($p[$i].Name -eq "svchost")
        {
            $p.RemoveAt($i)
            $i--
        }    
    }

 

Summary

For most scripts, you don’t manipulate arrays directly. Instead, you use Where-Object to create a new collection that includes only the items that you need. But if you need to manage an array directly, you can add and remove objects.

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.

[Google+] [Twitter] [Facebook] [LinkedIn] [StumbleUpon] [Digg] [Reddit] [Google Bookmark]

Get-LanguageKeywords: Using ConvertFrom-String on About Topics

$
0
0

There have been some great blog posts about the new ConvertFrom-String cmdlet in the Windows PowerShell 5.0 preview. My favorite learning tool is an excellent talk by Windows PowerShell MVP Tobias Weltner about the full range of text parsing options available in Windows PowerShell 3.0 – 5.0. While learning, I had some fun converting my electric bill and phone bill to PowerShell objects so that I could track them, but I really wasn’t using it for work.

Until today.

I wanted to find out whether the new enum keyword in the Windows PowerShell 5.0 preview had been added to the about_language_keywords help topic. I ran Update-Help and then used the Select-String cmdlet to search for enum keyword. I used the Quiet parameter of Select-String, because I wanted a $True/$False response.

Get-Help about_language_keywords | Out-String -Stream | Select-String "enum" –Quiet

You can use the same Select-String command on the help topic file.

dir $pshome\en-us\about_language_keywords.help.txt | Select-String “enum” -Quiet

Both of the commands returned $False, but that’s to be expected, since Windows PowerShell 5.0 is still in preview.

But, I’d really like to be able to manage those keywords in the same way that I manage type accelerators.

[PSObject].Assembly.GetType(“System.Management.Automation.TypeAccelerators”)::get

Or,  check for approved verbs:

Get-Verb

In short, I wanted to create a keyword object that I could use in Windows PowerShell. There’s a table in the about_language_keywords help file, but I couldn’t find the contents of that table anywhere else. The About topics are just text files and the table is just a series of text strings, so I looked in my handy toolkit and pulled out ConvertFrom-String.

Isolate the Keyword Table

The brand-new ConvertFrom-String cmdlet takes text strings and returns custom objects (PSCustomObject) in which the text strings are values of object properties.

I tried to create a custom object for each row of the Keyword-Reference table. But when I ran ConvertFrom-String on the entire help topic, I got a very pleasant error message saying that it was too complex and offering to help me with it. In the meantime, I set about isolating the Keyword-Reference table in the help topic.

image

I saved the entire about_language_keywords help topic content in the $help variable.

$help = Get-Content $pshome\en-us\about_language_keywords.help.txt

My first task was to find the line number of the first table entry (Begin…) in the Keyword-Reference table. I could have counted the number of lines from the top of the file or searched for “Begin”, but wanted to make the technique durable – not dependent on the current version of the file. I decided to search for the Keyword-Reference header.

I used the Select-String cmdlet and a regular expression to find the Keyword-Reference table header. To get its line number, I used the LineNumber property of the MatchInfoobject that Select-String returns. The first table entry is two lines after the header, so I added 2 and saved the line number of the first table entry in the $start variable.

$start = (($help | Select-String -Pattern 'Keyword\s{7,}Reference').LineNumber) + 2

Next, I needed the line number of the last table entry, so I looked for the first blank line after the table. (The ^\s*$ is the regular expression for a blank line.) The last table entry is just before the blank line, so I subtracted 1 from the line number, then added the result to the $start value to get the line number of the last table entry from the beginning of the file.

$end = $start + (($help[($start - 1) .. 999] | 
    Select-string -Pattern '^\s*$').LineNumber | 
    Select-Object -First 1) - 1

With the $start and $end values, I’ve captured the table in the $table variable. (Remember to subtract 1 to convert each line number to an array index.)

$table = $help[($start – 1)..($end – 1)]

image

Now, the fun begins.

 

Create a template

The ConvertFrom-String cmdlet takes text strings and returns custom objects (PSCustomObject) in which the text strings are values of object properties. I wanted each custom object to have Keyword and Reference properties. The values of those properties are the keyword and reference strings in the table.

(Remember that Keyword and Reference are just names that I chose. You can use any names for the properties.)

        Keyword            Reference
        -----------        -------------------------------------------
	Begin              about_Functions, about_Functions_Advanced        
        Break              about_Break, about_Trap                          
        Catch              about_Try_Catch_Finally                          
        Continue           about_Continue, about_Trap                       
        Data               about_Data_Sections                              
        Do                 about_Do, about_While

 

The $help variables contains text strings, so I needed to tell ConvertFrom-String which parts of each text string go with the Name property, which parts go with the Reference property, and which parts to ignore. To do that, you use a template, which is a model for the custom objects that you create. The template consists of series of labeled examples that say, in essence, I want it my objects to look like this.

Let’s begin with the first row of the table.

  • To map the Begin string to the Keyword property, surround the string with curly braces and prepend the property name (Keyword) and a colon (:).
    {Keyword:Begin}   about_Functions, about_Functions_Advanced
  • To indicate that Keyword is the first property of each new object, append an asterisk (*) to the property name.
    {Keyword*:Begin}   about_Functions, about_Functions_Advanced
  • Use the same process to map the Reference property to the reference string. (You don’t need the asterisk here.)
    {Keyword*:Begin}   {Reference:about_Functions, about_Functions_Advanced}
  • Don’t label the spaces between the keyword and reference strings, because we want ConvertFrom-String to ignore them. ConvertFrom-String excludes unlabeled characters from the custom object.
	{Keyword*:Begin}   {Reference:about_Functions, about_Functions_Advanced}
        Break              about_Break, about_Trap                          
        Catch              about_Try_Catch_Finally                          
        Continue           about_Continue, about_Trap                       
        Data               about_Data_Sections                              
        Do                 about_Do, about_While

 

Because the template is a series of examples, you don’t need to label every line of the table. Label only the lines that are parsed differently from the labeled line. ConvertFrom-String generates parsing rules, so use your labeling to help it generate accurate rules. (To see the rules that it generates, use the Verbose parameter.)

[Tip: You must label at least two lines, even when all lines are identical. Otherwise the parsing fails.]

For example, the reference text in the Begin row includes two phrases separated by a comma. The reference text in the Catch row has just one about phrase, so I labeled it.

	{Keyword*:Begin}   {Reference:about_Functions, about_Functions_Advanced}
        Break              about_Break, about_Trap                          
        {Keyword*:Catch}   {Reference:about_Try_Catch_Finally}
        Continue           about_Continue, about_Trap                       
        Data               about_Data_Sections                              
        Do                 about_Do, about_While

 

I also labeled Exit, because its reference didn’t begin with “about” and it ends with a period. And, I labeled Workflow, because it was the last row. (Because its rules for ending a line rely on the characters that begin the next line , ConvertFrom-String tends to miss the last line.)

Then, I deleted all of the unlabeled items from the template, placed the template in a here-string, and saved it in a $template variable. You can also save a template in a text file, but using a variable in the script guarantees that the template is available to anyone who uses the script.

$template = @"
 
        {Keyword*:Begin}    {Reference:about_Functions, about_Functions_Advanced}
        {Keyword*:Catch}    {Reference:about_Try_Catch_Finally}
        {Keyword*:Exit}     {Reference:Described in this topic.}
        {Keyword*:Workflow} {Reference:about_Workflows}
"@

 

Run ConvertFrom-String

The syntax of a ConvertFrom-String command is pretty simple. Pipe the content to be parsed to the cmdlet. Use the TemplateContent or TemplateFile parameters to identify the template.

PS C:\>$table | ConvertFrom-String -TemplateContent $template
PS C:\>$table

ExtentText                                       Keyword      Reference
----------                                       -------      ---------
Begin about_Functions, about_Functions_Advan.. . Begin        about_Functions, about_Functions_Advanced
Break about_Break, about_Trap.. .                Break        about_Break, about_Trap
Catch about_Try_Catch_Finally.. .                Catch        about_Try_Catch_Finally
Continue about_Continue, about_Trap.. .          Continue     about_Continue, about_Trap
...

 

In addition to the Keyword and Reference properties, the output object includes an ExtentText property that contains all of the labeled text strings. ExtentText is very useful for debugging ConvertFrom-String output, but I don’t want it in my custom object. To exclude it, I use Select-Object to create a custom object with only the Keyword and Reference properties.

PS C:\>$table | ConvertFrom-String -TemplateContent $template 
    | Select-Object -Property Keyword, Reference

    Keyword              Reference
    -------              ---------
    Begin                about_Functions, about_Functions_Advanced
    Break                about_Break, about_Trap
    Catch                about_Try_Catch_Finally
    Continue             about_Continue, about_Trap
    ...

Done!

Examine the Custom Objects

Let’s examine at the custom objects that ConvertFrom-String returned. They have the Keyword and Reference properties that I defined.

PS C:\> $k = $table | ConvertFrom-String -TemplateContent $template 
    | Select-Object -Property Keyword, Reference

PS C:\> $k | Get-Member

       TypeName: Selected.System.Management.Automation.PSCustomObject

    Name        MemberType   Definition                                                       
    ----        ----------   ----------                                                       
    Equals      Method       bool Equals(System.Object obj)                                   
    GetHashCode Method       int GetHashCode()                                                
    GetType     Method       type GetType()                                                   
    ToString    Method       string ToString()                                                
    Keyword     NoteProperty System.String Keyword=Begin                                      
    Reference   NoteProperty System.String Reference=about_Functions, about_Functions_Advanced

 

And, I can use them to explore the keywords in Windows PowerShell.

PS C:\> $k.Keyword.Contains("Catch")
True

PS C:\> $k | where Keyword -eq "Catch"

Keyword                Reference                                                                                 
-------                ---------                                                                                 
Catch                  about_Try_Catch_Finally                                                                   

PS C:> Get-Help ($k | where Keyword -eq "Catch").Reference
    about_Try_Catch_Finally

    PS C:\> Get-Help (($k | where Keyword -eq "Catch").Reference)
    TOPIC
        about_Try_Catch_Finally

    SHORT DESCRIPTION
        Describes how to use the Try, Catch, and Finally blocks to handle 
        terminating errors.

    LONG DESCRIPTION
        Use Try, Catch, and Finally blocks to respond to or handle terminating 
        errors in scripts. The Trap statement can also be used to handle 
    ...

 

Summary

Here’s a review of the process that I used. I wanted to grab the table from the middle of the about_language_keywords help topic and convert each line of the table into an object.

  • I started with the content of the about_language_keywords topic.
$help = Get-Content $pshome\en-us\about_language_keywords.help.txt
  • To isolate the table in the topic, I used the Select-String cmdlet and the LineNumber property of the MatchInfo (Microsoft.PowerShell.Commands.MatchInfo) object that Select-String returns. When indexing into the help, I just subtracted 1 from the line numbers.
$start = (($help | Select-String -Pattern 'Keyword\s{7,}Reference').LineNumber) + 2

$end = $start + (($help[($start - 1) .. 999] | 
    Select-string -Pattern '^\s*$').LineNumber | 
    Select-Object -First 1) – 1

$table = $help[($start - 1)..($end - 1)]
  • I created a template of labeled strings with Keyword and Reference properties. The labels indicate which text strings go with each property. ConvertFrom-String uses the examples in the template to generate rules for parsing the text strings.
$template = @"
        {Keyword*:Begin}    {Reference:about_Functions, about_Functions_Advanced}
        {Keyword*:Catch}    {Reference:about_Try_Catch_Finally}
        {Keyword*:Exit}     {Reference:Described in this topic.}
        {Keyword*:Workflow} {Reference:about_Workflows}
"@
  • I ran the ConvertFrom-String cmdlet with the template.
$table | ConvertFrom-String -TemplateContent $template | 
    Select-Object -Property Keyword, Reference

Finally, I dropped the commands into a script as saved it. Now, I can use it repeatedly. And when the enum keywords shows up in help, I’ll have it, too.

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.

 

Get-LanguageKeywords.ps1

You can copy the script code here or download a ZIP file from the SAPIEN Downloads site.

To download, sign in, in the left nav, click Sample Scripts, click Get-LanguageKeywords.ps1.zip, and in the top right corner, click Download.

 

<#    
    .NOTES
    ===========================================================================
     Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2014 v4.1.74
     Created on:       11/25/2014 4:10 PM
     Organization:     SAPIEN Technologies, Inc.
     Contact:       June Blender, juneb@sapien.com, @juneb_get_help
     Filename:         Get-LanguageKeywords.ps1
    ===========================================================================
    .SYNOPSIS
        Creates custom objects from the Keyword-Reference table in the
        about_Language_Keywords help topic.

    .DESCRIPTION
        The Get-Language keywords script gets the Keyword-Reference table
        from the about_Language_Keywords help topic. It uses the ConvertFrom-String
        cmdlet to convert each row of the table to a custom object, and returns
        the custom objects.

        It takes no parameters, but it requires the about_Language_Keyword help
        topic and Windows PowerShell version 5.0.

    .INPUTS
        None

    .OUTPUTS
        System.Management.Automation.PSCustomObject

    .EXAMPLE
        $keywords = .\Get-LanguageKeyword.ps1
        
        $keywords.Keyword.Contains("While")
        True
        
        $keywords.Keyword.Contains("Grapefruit")
        False
        
        $keywords | where Name -eq "Finally"| Format-Table -Autosize
        Keyword                  Reference
        ----                     ---------
        Finally                  about_Try_Catch_Finally

        Get-Help ($keywords | where Name -eq "Finally").Reference
#>

#Requires -Version 5

$template = @"

        {Keyword*:Begin}   {Reference:about_Functions, about_Functions_Advanced}
        {Keyword*:Catch}       {Reference:about_Try_Catch_Finally}
        {Keyword*:Exit}    {Reference:Described in this topic.}
        {Keyword*:Workflow}          {Reference:about_Workflows}
"@

if (!($help = dir $pshome\en-us\about_language_keywords.help.txt))
{
    throw "Can't find the about_language_keywords help topic. Run Update-Help and try again."
}

#Get the line number of the Keyword-Reference table header
#Add 2 to get the line number of the first entry in the table
$start = (($help | Select-String -Pattern 'Keyword\s{7,}Reference').LineNumber) + 2

#Get the line number of the first blank line after the table
#Subtract one to get the line number of the last table entry
$end = $start + (($help[($start - 1) .. 999] | Select-string -Pattern '^\s*$').LineNumber | Select-Object -First 1) - 1

#Get only the Keyword-Reference table
#Subtract 1 from each line number to get the indexes
$table = $help[($start - 1) .. ($end - 1)]

#Create a custom object from the Keyword-Reference table
$table | ConvertFrom-String -TemplateContent $template | Select-Object -Property Keyword, Reference
[Google+] [Twitter] [Facebook] [LinkedIn] [StumbleUpon] [Digg] [Reddit] [Google Bookmark]

Displaying Output in a GUI Application

$
0
0

After many years of writing scripts for the Windows PowerShell console, I’m now learning the next level of automation — writing GUI applications that run Windows PowerShell commands. PowerShell Studio forms provide a gentle and well-guided introduction to GUI applications, but it’s still taken me some time to break old habits.

I constantly need to remind myself that things that “just work” in the console don’t make any sense in a GUI. Writing output is one of them.

Writing Output to the Console

When you generate output in the Windows PowerShell console, the output appears as text strings in the console window.

PS C:> Get-Date
Wednesday, December 3, 2014 7:16:38 AM

Behind the scenes, Windows PowerShell silently appends an Out-String command to the pipeline and sends the results to “standard output” (“stdout”). The process that hosts Windows PowerShell handles the standard output, almost always by writing the output to the console (host behavior might vary).

So, if you run a cmdlet that returns objects, such as the Get-Process cmdlet, (string representations of) the objects appear in the console.

If you format objects, such as by using Format-List or Format-Table, (string representations of) the formatted objects appear in the console.

If you run Write-Host or Write-Output, Out-Host or Out-String, the output strings appears in the console.

And even if you send the output to an alternate output stream by using Write-Warning, Write-Verbose, or Write-Error, by default, the output appears in the console.

But GUI application don’t have a console. What happens to the output that would otherwise go to the console when there is no console? And, more importantly, how to I manage output without a console.

Where is my output?

GUI applications, like the cool ones that you write in PowerShell Studio, don’t have a console. There is no standard output. There is no auto-appended Out-String.

So, when you generate output and don’t save it to a file or a variable, the output is just lost.

Get-Process | ... <Nowhere> ...

It’s like a pitcher throwing a baseball when there’s no catcher on the field. The ball just falls to the ground and rolls to a stop .. ignored.

clip_image002

So, in a GUI application, you can’t just write output and hope for the best. If you want to display cmdlet output or save it, you need to do it explicitly.

Display output in a text box

A GUI application is a collection of controls. To display output, you need to assign the output to a control, such as text box, rich text box, or a data grid.

For example, to display the name of the local computer in the console, you run an $env:ComputerName command.

PS C:> $env:ComputerName
JUNEB-TESTBOX

When I try the same thing in a GUI application where there is no console, I don’t see any result at all. In this example, I created a form with a button that, when clicked, runs the $env:ComputerName expression.

$buttonComputerName_Click = {
    <b>$env:ComputerName</b>
}

But, when I click the button, nothing happens, even if I have a console window open.

GetComputerName

To display the computer name in a GUI application, I add a display control to my form and assign the output of the command to a method of the display control.

For example, I can add a Textbox control to the form and use the assignment operator (=) to assign the result of the expression to the Text property of the TextBox. Now, when you click the button, you see the output in the TextBox.

$buttonComputerName_Click = {
    $textboxResults.Text = $env:ComputerName
}

Here’s the result:

GetComputerName2

Display Output in a Different Program

You can also display the output of commands in objects that are not part of your program. GUI professionals think that this approach is sloppy and not a best practice, but you might find it useful for testing or debugging.

For example, you can pipe the output to the Out-GridView cmdlet.

$buttonComputerName_Click = {
    $env:ComputerName | Out-GridView
}

Here’s the result:

JuneTestBox

You can also write to a file and open the file. These sample commands create a temporary file, write to the file, and then use Notepad to display the file. Again, this makes real programmers cringe, but it works, at least for testing.

$buttonComputerName_Click = {
    $TmpFile = [System.IO.Path]::GetTempFileName
    $env:COMPUTERNAME | Set-Content -Path $TmpFile
    Notepad $TmpFile
}

Here’s the result:

JuneTestBox2

Convert to Strings

Another task you need to manage in a GUI application is converting objects to text strings. Usually, this just means piping your output to an Out cmdlet, such as Out-String. Out cmdlets, including Out-GridView, convert objects to string data.

When you write output to the console, Windows PowerShell automatically appends an Out-String command to the pipeline. But, there is no console and no automatic Out-String in a GUI application.

When you assign non-string objects to a string object or to a control that takes string objects, such as a TextBox, Windows PowerShell calls the toString() method of the object. (In other languages, the assignment would just fail, but it’s trying to help.) With some objects, such as DateTime and ErrorRecord objects, toString() works just fine, but, in many cases, it produces odd-looking results.

For example, this command assigns the results of a Get-Process command to a text box. Windows PowerShell recognizes that you need text and calls the toString() method of Process objects, but the result is not useful.

$buttonGetProcess_Click = {
    $textboxResults.Text = Get-Process
}

GetProcess1

To get a string representation of the default process table, pipe the output to the Out-String cmdlet and then assign it to the text box.

$buttonGetProcess_Click = {
    $textboxResults.Text = Get-Process | Out-String
}

GetProcess2

TIP: To make the output easy to read, set the text box (or rich text box) to use a monospace font. In PowerShell Studio, right click the text box, click Apply Property Set, and then click Apply Console Font.

Don’t avoid toString() completely. There are a few objects where the toString() method produces a useful result. DateTime objects are the classic example. The toString() method of a DateTime object returns a “short-date/long-time” format string. Out-String returns a “long-date/long-time” format.

PS C:> (Get-Date).ToString()
12/6/2014 10:42:24 AM

PS C:> Get-Date | Out-String
Saturday, December 6, 2014 10:42:37 AM
#Uses toString()
$buttonGetDate_Click = {
    $textboxDate.Text = Get-Date
}

GetDate3

#Uses toString()
$buttonGetDate_Click = {
    $textboxDate.Text = Get-Date | Out-String
}

GetDate2

You can also use the Formatand UFormat parameters of the Get-Date cmdlet to get a formatted date string in almost any configuration. For example, to get a date in RFC1123 format, use -Format R (or “r”). No need to pipe to Out-String when you use Format or UFormat, because these parameters force the cmdlet to return a string.

PS C:> Get-Date -Format R
Tue, 09 Dec 2014 10:46:41 GMT

GetDate1

Converting Formatted Objects

In the console, Format cmdlets, such as Format-Table, are usually the last element in a pipeline. The output looks great in the console, because Windows PowerShell appends an Out-String command to the pipeline before sending it to the console.

PS C:> Get-HotFix | Format-Table -Property HotFixID, InstalledOn -AutoSize

HotFixID     InstalledOn
--------     -----------
KB2693643    10/7/2014 12:00:00 AM
KB2769299    9/5/2014 12:00:00 AM
KB2883200    10/7/2013 12:00:00 AM
KB2883684    9/5/2014 12:00:00 AM
...

But, in a GUI application, by default, you get the result of calling the toString() method of the format objects that the Format cmdlets return.

$buttonGetHotfix_Click = {
    $textboxHotFix.Text = Get-HotFix |
    Format-Table -Property HotFixID, InstalledOn -AutoSize
}

GetHotfix1

To get a more usable display in your GUI application, pipe the format objects to the Out-String cmdlet before assigning them to the Text property of the TextBox.

$buttonGetHotfix_Click = {
    $textboxHotFix.Text = Get-HotFix |
    Format-Table -Property HotFixID, InstalledOn -AutoSize |
    Out-String
}

GetHotFix2

That’s better!

After a while, the task of managing the output display in your GUI applications become as natural as typing a cmdlet name, but while you’re learning, be sure to test and make sure that all of your output is captured and displayed in a usable format.

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.

 
[Google+] [Twitter] [Facebook] [LinkedIn] [StumbleUpon] [Digg] [Reddit] [Google Bookmark]

Find variables with class

$
0
0

I’ve been playing with the Windows PowerShell 5.0 preview and especially with its new class feature. After I create my class, I often create several instances of the class and experiment with them in my session. For example, I created a Wine class and then created several instances of my wine class. I saved each Wine instance in a variable.

$PSWine = [Wine]::new("PSCabernet")
$Shiraz = [Wine]::new()
...

After playing with the instances for a while (and enjoying a few sips of PSWine), I couldn’t remember all of the Wine objects that I’d created in my session. They were all in variables, so I used the Get-Variable cmdlet to find the $PSWine variable.

PS C:\> Get-Variable –Name PSWine
Name               Value
----               ------
PSWine             Wine

Excellent! The value of the variable is the name of the class. This should make it easy to find the other variables.

PS C:\> Get-Variable | Where Value –eq "Wine"

Name                           Value
----                           -----
?                              True
true                           True

That didn’t work, because the value of the Value property appears to contain “Wine”, but that’s just a display artifact. The Value of the variable is the entire Wine object.

PS C:\> (Get-Variable -Name PSWine).Value

Name        : PSCabernet
Winery      : Chateau Snover
Year        : 2012
isSparkling : False
Color       : Red
Sweetness   : VeryDry
Description : Pithy
Rating      : {96, 94}

To get Wine objects in the session, or objects of any particular type, such as strings or integers, call the GetType() method of the value and then take its Name property.

PS C:\> Get-Variable | where  {$_.value.gettype().Name -eq "Wine"}

Name                           Value
----                           -----
Duck                           Wine

You cannot call a method on a null-valued expression.
At line:1 char:24
+ Get-Variable | where  {$_.value.gettype().Name -eq "Wine"}
+                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull

PSWine                         Wine
Shiraz                         Wine

Oops! You have to account for variables that don’t have a value. I’ll use the AND operator to make sure that the variable has a value before I try to get the name of its value type. Windows PowerShell has a “lazy” AND operator, which means that if the first part of the AND statement isn’t true, since both parts must be true, it doesn’t even check the second part.

PS C:\ps-test> Get-Variable | where {$_.Value -and $_.Value.GetType().Name -eq "Wine"}

Name                           Value
----                           -----
Duck                           Wine
PSWine                         Wine
Shiraz                         Wine

It works on all types.

PS C:\ps-test> Get-Variable | 
where {$_.Value -and $_.Value.GetType().Name -eq "ActionPreference"}

Name                           Value
----                           -----
ErrorActionPreference          Continue
ProgressPreference             Continue
WarningPreference              Continue

Let’s turn this into a function:

function Get-VariableOfType ($Type)
{
    Get-Variable | where {$_.Value -and $_.Value.GetType().Name -eq  $type}
}

Now, back to that Wine glass —  I mean class!

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.

 
[Google+] [Twitter] [Facebook] [LinkedIn] [StumbleUpon] [Digg] [Reddit] [Google Bookmark]

Enumerators in Windows PowerShell 5.0

$
0
0

Enumerators have always been a part of Windows PowerShell, because they’re an important part of the .NET Framework. But they’re about to become even more popular, because changes in Windows PowerShell 5.0 make them very easy to create.

———–

Enumerators (“enums”) define a set of values, knows as “members” of the enumeration. You use enums to create a set of valid values and prohibit all other values. In Windows PowerShell, you can use enumerators as types, just as you use .NET classes. For example, the value of the ExecutionPolicy parameter of the Set-ExecutionPolicy cmdlet has a type of ExecutionPolicy and its Scope parameter has a type of ExecutionPolicyScope.

Set-ExecutionPolicy [-ExecutionPolicy] <ExecutionPolicy> [[-Scope] <ExecutionPolicyScope>] [-Force] [-WhatIf] [-Confirm] [<CommonParameters>]

 
Both ExecutionPolicy and ExecutionPolicyScope are enumerators. The enum members are listed and defined on the MSDN page for each enumerator.

image

If you try to use a value that is not a member of the enumeration, you get a very helpful error. The error explains that the value is limited to the enumeration and lists the valid values. So, a quick way to find the members of an enumeration is to try a value that is clearly not valid, like good old “foo”.

[ADMIN]: PS C:\ps-test> Set-ExecutionPolicy -ExecutionPolicy foo
Set-ExecutionPolicy : Cannot bind parameter 'ExecutionPolicy'. Cannot convert value "foo" 
to type "Microsoft.PowerShell.ExecutionPolicy". Error: "Unable to match the identifier 
name foo to a valid enumerator name. Specify one of the following enumerator names and try 
again: Unrestricted, RemoteSigned, AllSigned, Restricted, Default, Bypass, Undefined"
At line:1 char:38
+ Set-ExecutionPolicy -ExecutionPolicy foo
+                                      ~~~
+ CategoryInfo          : InvalidArgument: (:) [Set-ExecutionPolicy], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.SetExecutionPolicyCommand

 

It’s easy to use enumerators. This excellent article from the TechNet Library explains it well. And, Windows PowerShell has let you define your own enumerators since 1.0, but it was a bit tricky. Not any more!

 

Create an enumerator

In Windows PowerShell 5.0, the enum keyword makes it much easier. Here’s a WineSweetness enumerator that I created for my Wine class.

enum WineSweetness
{
    VeryDry
    Dry
    Moderate
    Sweet
    VerySweet
}

 

I use it to limit parameter values, much like ValidateSet, but unlike ValidateSet, once it’s defined in my session, script, or module, I can use it repeatedly without redefining it. In this example, I use it to limit the values of the Sweetness property of instances of my Wine class.

class Wine
{
    #Properties
    [String]$Name
    [WineSweetness]$Sweetness
    ...
}

 
I can also use it functions, such as my Get-Wine function, without redefining it.

function Get-Wine
{
    Param
    (
    [parameter(Mandatory = $false)]
    [WineSweetness]
    $SweetnessValue,
    ...
}

 

Enum syntax

The enum syntax is so easy. Just note that the list is NOT comma-separated. If you prefer to list the values on a single line, use semi-colons.

enum <Name>
{
    Value1
    [Value2...]
}

-or-

enum <Name>
{
    Value1; [Value2...;]
}

 

The value names cannot include spaces or special characters, although an underscore (_) is permitted. Enclosing a names with spaces in quotation marks or curly braces doesn’t help. If you include a space, Windows PowerShell thinks it’s an expression, which is permitted.

enum WineSweetness
{
    VeryDry
    Dry
    Moderate
    Sweet
    Very Sweet   # <--- No spaces
}
Very : The term 'Very' is not recognized as the name of a cmdlet, function, script file, 
or operable program. Check the spelling of the name, or if a path was included, verify 
that the path is correct and try again.
At line:1 char:5
+     Very Sweet
+     ~~~~
+ CategoryInfo          : ObjectNotFound: (Very:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException

 

The values cannot be numbers and the first character of a value name cannot be a number. The ‘ missing closing “}” ‘ error is not very helpful, so watch out for this one.

PS C:\> enum WineSweetness
{
    VeryDry
    Dry
    3            # <------ No numbers
    Sweet
    VerySweet
}
At line:4 char:8
+     Dry
+        ~
Missing closing '}' in statement block or type definition.
At line:8 char:1
+ }
+ ~
Unexpected token '}' in expression or statement.
+ CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingEndCurlyBrace

 

Enums have integer values

Every enumerator value has a corresponding integer value. When you create an enumerator in Windows PowerShell (by using the Add-Type cmdlet or the enum keyword), the parser assigns a default integer value to each enum value. These default integer values begin with zero and increase by 1. The values are assigned in the order they are listed in the enumerator.

For example, when I created my WineSweetness enum, Windows PowerShell assigned the values 0 – 4 to the enum values.

enum WineSweetness
{
    VeryDry
    Dry
    Moderate
    Sweet
    VerySweet
}

 

To find the values in any enumerator, call the GetValues static method of the System.Enum type:

PS C:\> [System.Enum]::GetValues([WineSweetness])

VeryDry
Dry
Moderate
Sweet
VerySweet

 

To find the underlying integer value of each enum value, cast the enum value to an integer (thanks to Keith Babinec for this trick).

PS C:\> [System.Enum]::GetValues([WineSweetness]) | foreach { [int]$_ }

0
1
2
3
4

 

Let’s put them together. This command creates a custom object (I love PSCustomObject!) with the names and integer value of each enumerator value.

PS C:\> [System.Enum]::GetValues([WineSweetness]) | 
foreach { [PSCustomObject]@{ValueName = $_; IntValue = [int]$_  }   }

ValueName              IntValue
---------              --------
VeryDry                       0
Dry                           1
Moderate                      2
Sweet                         3
VerySweet                     4

 

You can also assign integer values, and expressions that return an integer value, to your enum values. The values that you assign override the default zero-based values. All enum values are constants, so you can’t change them after you assign them.

PS C:\> enum WineSweetness
{
    Sweet = 4
    VeryDry = 2 * 4 + 2
    Moderate = 6
    VerySweet = 2
    Dry = 8
}

[System.Enum]::GetValues([WineSweetness]) | 
foreach {[PSCustomObject]@{ValueName = $_; IntValue = [int]$_} }

ValueName                  IntValue
---------                  --------
VerySweet                         2
Sweet                             4
Moderate                          6
Dry                               8
VeryDry                          10

 

Using an enumerator

When you use an enumerator, you can specify either a value name or its integer value.

enum WineSweetness
{
    VeryDry = 1
    Dry = 2
    Moderate = 3
    Sweet = 4
    VerySweet = 5
}

 

I’ll create an instance of my Wine class and set its Sweetness property, which has a type of WineSweetness, to VeryDry (my personal favorite).

PS C:\>$PSWine = [Wine]::new("PSCabernet")
PS C:\>$PSWine.Sweetness = VeryDry

 
I can use the dot method to get the string representation of the Sweetness property value:

PS C:\> $PSWine.Sweetness
VeryDry

 
Or, use the Value__ (that’s 2 underscores) property of all enum values to get its integer value.

PS C:\> $PSWine.Sweetness.value__
1

 
If I try to change the value of the Sweetness property to a value that’s not in the enumerator, I get that awesome error that lists the valid values.

PS C:\ps-test> $PSWine.Sweetness = "TooSweet"
Exception setting "Sweetness": "Cannot convert value "TooSweet" to type "WineSweetness". 
Error: "Unable to match the identifier name TooSweet to a valid enumerator name. 
Specify one of the following enumerator names and try again:
VeryDry, Dry, Moderate, Sweet, VerySweet""
At line:1 char:1
+ $PSWine.Sweetness = "TooSweet"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : NotSpecified: (:) [], SetValueInvocationException
+ FullyQualifiedErrorId : ExceptionWhenSetting

 
Just so I use a valid value, I can assign either a string or a integer value.

PS C:\> $PSWine.Sweetness = Dry
PS C:\> $PSWine.Sweetness
Dry

PS C:\> $PSWine.Sweetness = 3
PS C:\> $PSWine.Sweetness
Moderate

When you sort objects by an enumerated property value, the Sort-Object cmdlet sorts by integer value. For example, I have some wines in a $myWines variable.

PS C:\ps-test> $myWines

Name        : Great Duck
Winery      : Escalante Winery
Year        : 2003
isSparkling : True
Color       : White
Sweetness   : VerySweet
Description : Rich and magnificent
Rating      : {98, 97}

 

Name        : PSCabernet
Winery      : Chateau Snover
Year        : 2006
isSparkling : False
Color       : Red
Sweetness   : VeryDry
Description : Pithy
Rating      : {96}

 

Name        : Shiraz
Winery      : SAPIEN Vineyards
Year        : 1990
isSparkling : False
Color       : Red
Sweetness   : Dry
Description : Bold and innovative
Rating      : {100}

 

When I sort them by the value of the Sweetness property, I get them in ascending integer value order. I used a calculated property to add the integer value to the table.

PS C:\ps-test> $mywines |
Sort-Object -Property Sweetness |
Format-Table -Property Name, `
Sweetness, `
@{Label="IntSweet"; Expression={$_.Sweetness.Value__}} -AutoSize

Name       Sweetness IntSweet
----       --------- --------
PSWine       VeryDry        1
Shiraz           Dry        2
Great Duck VerySweet        5

 

The introduction of the enum keyword has really made enumerations available to everyone. Yet another tool for your Windows PowerShell toolbox.

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.

 
[Google+] [Twitter] [Facebook] [LinkedIn] [StumbleUpon] [Digg] [Reddit] [Google Bookmark]
Viewing all 308 articles
Browse latest View live