Tuesday, November 26, 2013

Hedgehog TDS - Package Version

I wanted to create TDS packages using the version from the project assembly version.
Here's a piece of code that I finally figured out how to accomplish dynamically. Place at the bottom of your .scproj file.

Friday, October 25, 2013

Sunday, September 22, 2013

Sitecore Integrated with Ace Code Editor

Check out my latest video showing you how to configure the EditHtml dialog with the Ace Code Editor!
(function($){
	$('head').append("<style>div[id$=RibbonPanel],textarea[id$=Html] { display: none; } #CodeEditor { width: 100%; height: 100%; } </style>");

	$(function() {
		var html = $('textarea[id$=Html]');
		var ce = $("<div id='CodeEditor' />");
        html.after(ce);

        var codeeditor = ace.edit(ce[0]);
        codeeditor.setTheme("ace/theme/monokai");
        codeeditor.session.setMode("ace/mode/html");
        codeeditor.setShowPrintMargin(false);

        codeeditor.session.setValue(html.val().trim());
        codeeditor.session.on('change', function () {
                html.val(codeeditor.session.getValue());
        });

        ace.config.loadModule("ace/ext/emmet", function () {
            ace.require("ace/lib/net").loadScript("/Scripts/ace/emmet-core/emmet.js", function () {
                codeeditor.setOption("enableEmmet", true);
            });

            codeeditor.setOptions({
                enableSnippets: true,
                enableBasicAutocompletion: true
            });              
        });

        ace.config.loadModule("ace/ext/language_tools", function (module) {
            codeeditor.setOptions({
                enableSnippets: true,
                enableBasicAutocompletion: true
            });
        });
	});
}(jQuery));

Links:
  • http://ace.c9.io
  • https://github.com/ajaxorg/ace-builds/
  • http://michaellwest.blogspot.com

Wednesday, September 11, 2013

Sitecore Generic Shortcodes

I really enjoyed reading Sitecore Junkie's post about using Shortcodes in Sitecore. Here's an example of one I built for generic item shortcodes.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Sitecore;
using Sitecore.Data;

namespace Concentra.Web.Configuration.Pipelines.ExpandShortcodes
{
    /// <summary>
    /// Expands shortcodes with the following format:
    /// [item id=110D559F-DEA5-42EA-9C1C-8A5DF7E70EF9 fieldname="Title"]
    /// </summary>
    public class ExpandItemShortcodes : ExpandShortcodesProcessor
    {
        public override IEnumerable<Shortcode> GetShortcodes(string content)
        {
            if (String.IsNullOrWhiteSpace(content))
            {
                return new List<Shortcode>();
            }

            var shortcodes = new List<Shortcode>();
            var matches = Regex.Matches(content, @"\[item id=(?<id>.*) fieldname=(?<fieldname>.*)\]", 
                RegexOptions.Compiled | RegexOptions.IgnoreCase);

            foreach (var match in matches.OfType<Match>().Where(m => m.Success))
            {
                shortcodes.Add(new Shortcode
                               {
                                   Unexpanded = match.Value,
                                   Expanded = GetItem(
                                       match.Groups["id"].Value,
                                       match.Groups["fieldname"].Value)
                               }
                    );
            }

            return shortcodes;
        }

        public string GetItem(string id, string fieldName)
        {
            if (!String.IsNullOrEmpty(id) && ID.IsID(id))
            {
                var item = Context.Database.GetItem(ID.Parse(id));
                if (item != null)
                {
                    var name = fieldName.Replace("\"", String.Empty).Replace("'", String.Empty);
                    if (item.Fields.Any(field => field.Name == name))
                    {
                        return item.Fields[name].Value;
                    }
                }
            }

            return String.Empty;
        }
    }
}

Saturday, September 7, 2013

Sitecore PowerShell Extensions Mixing C# and PowerShell



$code = @"
using System;

namespace AwesomeNamespace {
    public enum AwesomeActivityType {
        Nothing,
        Sleeping,
        Eating,
        UsingSPE
    }
    public class AwesomeClass {
        public string DoSomethingAwesome(AwesomeActivityType activity) {
            return String.Format("Your awesome activity is {0}. That's awesome!", activity);
        }
        
        public static string DoSomethingAwesomeAnytime(){
            return "There, their, the're";
        }
    }
}
"@
Add-Type $code

$awesome = New-Object AwesomeNamespace.AwesomeClass
$awesome.DoSomethingAwesome([AwesomeNamespace.AwesomeActivityType]::UsingSPE)
[AwesomeNamespace.AwesomeClass]::DoSomethingAwesomeAnytime()

Wednesday, September 4, 2013

Sitecore PowerShell Extensions Unlock Items

Below is an example of how to unlock all items under the Content tree.
 
# Find all the items under content recursively, then only return the properties you want. Here we only want items that are locked.
gci master:\content -rec | where { $_.Locking.IsLocked() } | 
    select Name, Id, @{n="IsLocked";e={$_.Locking.IsLocked()}}
 

The following aliases or shortened commands were used:
  • gci = Get-ChildItem
  • where = Where-Object
  • select = Select-Object
Note: To autosize the table pipe the output to the following command: ft -auto
 
# Unlock all the items.
gci master:\content -rec | where { $_.Locking.IsLocked() } | % { $_.Locking.Unlock() }
 

Tuesday, September 3, 2013

Sitecore PowerShell Extensions Kick Users

Here's a quick way to kick users :)
# Use the static class to get the list of sessions then for each session kick the user using the session id.
[Sitecore.Web.Authentication.DomainAccessGuard]::Sessions | 
    % { [Sitecore.Web.Authentication.DomainAccessGuard]::Kick($_.SessionId) }

Saturday, August 31, 2013

Sitecore PowerShell Extensions Packages and Serialization

I'm at it again. Had fun today creating packages and serializing items with SPE.
Demo Code:

# Serialization
Get-Item -Path "master:\templates\spe\" | Serialize-Item -Recurse

# Packages
$name = "dinner"
$package = New-Package -Name $name

$source = Get-Item "master:\templates\spe" | New-ItemSource -Name "Dinner Plates" -InstallMode Overwrite -MergeMode Merge
$package.Sources.Add($source)

$package | Export-Package -Path "$name.xml"
$package | Export-Package -Path "$name.zip" -Zip

Saturday, August 24, 2013

Sitecore PowerShell Extensions Creating Functions

Creating a function. Code below.

function Clear-SCArchive {
    <#
        .SYNOPSIS
             Clears entries from the archive. Defaults to a 30 retention period for the recyclebin.
             
        .EXAMPLE
            Remove all items 30 days or older.
            
            PS master:\> Clear-SCArchive
            
        .NOTES
            Michael West
            michaellwest.blogspot.com
            @MichaelWest101

            about_Comment_Based_Help
            about_Comparison_Operators
            about_Functions_Advanced
            about_Functions_Advanced_Parameters
            about_Functions_CmdletBindingAttribute
    #>
    [CmdletBinding()]
    param(
        [ValidateNotNullOrEmpty()]
        [string]$Name = "recyclebin",
        
        [int]$Days = 30
    )
    
    $expired = [datetime]::Now.AddDays(-1 * [Math]::Abs($Days))
    
    foreach($archive in Get-Archive -Name $Name) {
        $entries = $archive.GetEntries(0, $archive.GetEntryCount())
        foreach($entry in $entries){
            if($entry.ArchiveLocalDate -le $expired) {
                Write-Log "Removing item: $($entry.ArchivalId)"
                $archive.RemoveEntries($entry.ArchivalId)
            } else {
                Write-Verbose "Skipping $($entry.Name) on date $($entry.ArchiveLocalDate)"
            }
        }
    }
}

Sitecore PowerShell Extensions Publishing

Sitecore PowerShell Extensions Variables

So so so so so so.. Lord help me.

Sitecore PowerShell Extensions Running Commands

Sounds like I have a lisp :) Need to work on my annunciation.

Companion video:

Friday, August 23, 2013

Find Active Directory Users with Duplicate EmployeeId

Today I needed to create a report of all Active Directory users with duplicate EmployeeId. The first thing I thought to try was using the -Unique parameter. Let's see all the commands that support it.
PS C:\> Get-Command -ParameterName Unique

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Cmdlet          Select-Object                                      Microsoft.PowerShell.Utility
Cmdlet          Sort-Object                                        Microsoft.PowerShell.Utility

This is great! I see two commands that will filter and return unique values. Let's see each in action.
PS C:\> 1,1,1,2,3,3 | Select-Object -Unique
1
2
3

Well what I really want to see is that 1 and 3 are returned for each instance. When talking in terms of Active Directory users, the EmployeeId will be duplicated but not the user, so this really isn't going to work.
In this example Sort-Object will work basically the same but also sort the list for us.
PS C:\> 3,3,2,1,1,1 | Sort-Object -Unique
1
2
3

Another option is to go through all the users and tally up each occurrence of the EmployeeId and then filter them out.
PS C:\> $values = 1,1,1,2,3,3
PS C:\> $ids = @{}; $values | ForEach-Object { $ids[$_] += 1 }
PS C:\> $filteredValues = $values | Where-Object { $ids[$_] -eq 1 }
PS C:\> $filteredValues
2

Since we want the duplicates returned we can change the -eq to -gt like the following:
PS C:\> $filteredValues = $values | Where-Object { $ids[$_] -gt 1 }
PS C:\> $filteredValues
1
1
1
3
3

Still with me? The performance gain is from using the hashtable $ids; really good for creating a quick lookup table. So now that we know how to find the duplicates, let's try that in AD. I added a little error checking to make sure that the user actually has a value in the EmployeeId field (hashtables will also freakout with empty keys).
PS C:\> $users = Get-ADUser -Filter { Enabled -eq $true } -Properties EmployeeId
PS C:\> $ids = @{}; $users | ForEach-Object { if($_.EmployeeId) { $ids[$_.EmployeeId] += 1 } }
PS C:\> $filteredUsers = $users | Where-Object { if($_.EmployeeId) { $ids[$_.EmployeeId] -gt 1 } }
PS C:\> $filteredUsers | Select-Object -Property SamAccountName, EmployeeId
SamAccountName    EmployeeId
--------------    ----------
Michael.L.West    1234567
Michael.West      1234567


Finally, you can pipe the last line to Export-Csv to create your report.

Thursday, August 22, 2013

Sitecore PowerShell Extensions Basic Usage

Basic run through on using the Console and ISE from within Sitecore.
# Get details on how to use the Get-Item command
Get-Help Get-Item
# Get the list of items under home and filter by creator
Get-ChildItem master:\content\home | Where-Object { $_."__Created By" -eq "sitecore\admin" }
# Get the list of Sitecore PowerShell Extension commands
Get-Command | Where-Object {$_.Implementingtype.FullName -match "Cognifide.PowerShell"} | 
    Select-Object -Property Name | Format-Table

Tuesday, August 20, 2013

Sitecore PowerShell Extensions Introduction

Finally put together an introduction to the Sitecore PowerShell Extensions module.

Thursday, July 25, 2013

Active Directory - Find Difference Between Group Membership For User

While getting access transferred from one user to another, you may need to know how the group memberships are different between two users.
Import-Module ActiveDirectory

$leaving = Get-ADUser -Identity John.Doe -Properties memberof | select -expand memberof
$promoted = Get-ADUser -Identity Michael.West -Properties memberof | select -expand memberof

Compare-Object -ReferenceObject $promoted -DifferenceObject $leaving

Tuesday, July 23, 2013

PowerShell Bible Scripture Function

I've been meaning to put this together for a while and just now got around to doing it. Here's a short function that calls the Bible.org Api for Bible scriptures. One idea I had was to plug this into my profile so that I could replace the Microsoft log with a scripture.
 
function Get-BibleVerse {
    <#
        .SYNOPSIS
            Calls the bible.org api and returns the specified scriptures.

        .DESCRIPTION
            Calls the bible.org api to return the specified book-chapter-verse,
            random verse, or verse of the day.

        .PARAMETER Random
            Indicates the scripture returned should be random.

        .PARAMETER VerseOfTheDay
            Indicates the scripture returned should be the verse of the day.

        .PARAMETER Book
            Indicates the book to return, such as Matthew, Marke, Luke, or John.

        .EXAMPLE
            PS C:\> Get-BibleVerse -Random

        .EXAMPLE
            PS C:\> Get-BibleVerse -VerseOfTheDay -Type Json -Formatting Plain

        .EXAMPLE
            PS C:\> Get-BibleVerse -Book Ephesians -Chapter 5 -Verse 25 -Type Json

        .NOTES
            Michael West
            07.23.2013
            http://michaellwest.blogspot.com

        .LINK
            http://labs.bible.org/api_web_service
    #>
    [CmdletBinding(DefaultParameterSetName="Default")]
    param(
        [Parameter(ParameterSetName="Random")]
        [switch]$Random,

        [Parameter(ParameterSetName="Votd")]
        [switch]$VerseOfTheDay,

        [Parameter(ParameterSetName="Default")]
        [ValidateNotNullOrEmpty()]
        [string]$Book="Genesis",
        
        [Parameter(ParameterSetName="Default")]
        [ValidateScript({$_ -gt 0})]
        [int]$Chapter = 1,

        [Parameter(ParameterSetName="Default")]
        [ValidateScript({$_ -gt -1})]
        [int]$Verse=1,

        [ValidateSet("Json","Xml","Text")]
        [string]$Type="Text",

        [ValidateSet("Full","Para","Plain")]
        [string]$Formatting="Plain"
    )

    $url = "http://labs.bible.org/api/?passage="

    if($PSCmdlet.ParameterSetName -eq "Votd") {
        $url += "votd"
    } elseif ($PSCmdlet.ParameterSetName -eq "Random") {
        $url += "random"
    } else {
        $url += "$($Book)+$($Chapter)"
        if($Verse) {
            $url += ":$($Verse)"
        }
    }
    $url += "&type=$($Type)&formatting=$($Formatting)"
    $url = $url.ToLower()

    $result = Invoke-WebRequest -Uri $url
    if($result) {
        $result.Content
    }
}
 
Update 07.24.2013 Add this to your profile to get the verse of the day.
    $scripture = (Get-BibleVerse -VerseOfTheDay -Type Json | ConvertFrom-Json)[0]
    "$($scripture.bookname) $($scripture.chapter):$($scripture.verse) $($scripture.text)"

Thursday, July 18, 2013

Add Users to AD Group Using First Initial

Today at work we had a need to add users to specific Active Directory groups based on the first letter of the first name. We'll be using a plain text file as an example for the list of Active Directory identities. Save the following text into a file called names.txt:
John.Doe
Jane.Doe
Jane.Smith
Then you will need to run this in the PowerShell ISE.
Import-Module ActiveDirectory

# Each row of the text file will be consider one object. The object being the Active Directory identity (SamAccountName).
$names = Get-Content c:\names.txt

# Set this to $false when you are ready to make the changes.
$whatIf = $true

foreach ($name in $names) {
    $user = Get-ADUser -Filter { SamAccountName -eq $name } -Properties MemberOf
    if($user) {
        # The groups object will contain a list of Active Directory groups by their distinguished name. 
        # (i.e. CN=GroupName_A-C,OU=Groups,OU=Company,DC=pri,DC=company,DC=com)
        $groups = $user | Select-Object -ExpandProperty MemberOf
        if(-not ($groups -like 'CN=GroupName_*')) {
            $groupName = ''
            switch -Regex($user.SamAccountName[0]) {
                # Match the first letter as a, b, or c.
                "[a-c]" { $groupName = 'GroupName_A-C' }
                # Match the first letter as d, e, f, or g.
                "[d-g]" { $groupName = 'GroupName_D-G' }
                "[h-k]" { $groupName = 'GroupName_H-K' }
                "[l-q]" { $groupName = 'GroupName_L-Q' }
                "[r-t]" { $groupName = 'GroupName_R-T' }
                "[u-z]" { $groupName = 'GroupName_U-Z' }
            }

            if($groupName) {
                "Adding $($user.SamAccountName) to the group $($groupName)"
                Add-ADGroupMember -Identity $groupName -Members $user.SamAccountName -WhatIf:$whatIf
            }
        } else {
            "Skipping $($user.SamAccountName) because they are already in the group $($groupName)"
        }
    } else {
        "$($name) does not exist"
    }
}

Monday, July 8, 2013

PoweShell Script Module

I put together a PowerShell Script Module some time ago and thought I would make it available for others. Hope it helps give you some ideas on creating your own. Click for more details.

Thursday, June 27, 2013

CSharp String to SQL Table

I have a problem at work in which I need to convert a comma separated string into a SQL temp table. You can easily add this to a function.
DECLARE @LoginNames VARCHAR(max)
SET @LoginNames = 'Michael,Rebecca'

-- Create a temp table with a single column called LoginName
DECLARE @temp AS TABLE (LoginName NVARCHAR(255))

IF ISNULL(@LoginNames, '') <> ''
BEGIN
    DECLARE @s NVARCHAR(max)
    WHILE LEN(@LoginNames) > 0
    BEGIN
        IF CHARINDEX(',', @LoginNames) > 0
        BEGIN
            SET @s = LTRIM(RTRIM(SUBSTRING(@LoginNames, 1, CHARINDEX(',', @LoginNames) - 1)))
            -- After parsing a single value from the list, insert into the temp table
     INSERT INTO @temp (LoginName) VALUES (@s)
            SET @LoginNames = SUBSTRING(@LoginNames, CHARINDEX(',', @LoginNames) + 1, LEN(@LoginNames))
        END ELSE
        BEGIN
            SET @s = LTRIM(RTRIM(@LoginNames))
            -- After parsing a single value from the list, insert into the temp table
     INSERT INTO @temp (LoginName) VALUES (@s)
            SET @LoginNames= ''
        END
    END
END

SELECT LoginName FROM @temp

Output:
LoginName
Michael
Rebecca

Tuesday, May 7, 2013

Scripting Games 2013 Advanced Event 2 - My Submission

If you have not already, I highly recommend you work on the events for the Scripting Games. I feel like I'm learning so much within a few short days mainly due to the fact that I have very specific requirements outlined by the event, as well as knowing that tons of people will potentially see my submissions. Today I'll be talking about it during the Lunch-n-Learn I host at work, so seeing constructive and accurate criticism is welcomed (username michaellwest). Here is what I have submitted, please let me know your thoughts on how I can improve the script.
  • The begin scriptblock contains a hashtable of settings to use for the function. The keys represent the Cim class name and the values represent the properties to return. You can use strings, hashtables, and scriptblocks.
  • Use CimSessionOption with the Dcom protocol to more reliably query Windows Server 2000-2008.
  • The nested foreach loops are not that great, however the keys and properties are few so the performance is still fine. Get-CimInstance is what takes a long time.
EDIT: I made an adjustment based on a post from Mike Robbins regarding the Physical memory.
function Get-ServerInventory {
    <#
        .SYNOPSIS
            Performs a hardware inventory on the specified server(s).
 
        .DESCRIPTION
            The values returned by the inventory process may be enhanced by adding to the settings hashtable in the begin scriptblock.
            The settings key is the class name. The settings values supported include string, hashtable, and scriptblock.
 
        .PARAMETER ComputerName
            Indicates the server(s) to perform a hardware inventory. The default value is localhost.
 
        .EXAMPLE
            Perform an inventory on localhost.
 
            PS C:\> Get-ServerInventory
 
            TotalPhysicalMemory : 21356912640
            ProcessorName       : Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz
            Version             : 6.1.7601
            SerialNumber        : 00371-OEM-8992671-00008
            ComputerName        : WIN7DEV01
            Cores               : 4
            Sockets             : 1
 
        .EXAMPLE
            Perform an inventory on 1-N servers with an array or using Get-Content.
 
            PS C:\> "WIN2K01","WIN2K02","WIN2008R201" | Get-ServerInventory | Format-Table -AutoSize
 
            TotalPhysicalMemory ProcessorName                            Version  SerialNumber            ComputerName Cores Sockets
            ------------------- -------------                            -------  ------------            ------------ ----- -------
                     4294148096 Intel(R) Xeon(R) CPU E5-2680 0 @ 2.70GHz 5.2.3790 69712-640-5906017-45214 WIN2K01          1       1
                     2146861056 Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz 5.2.3790 69712-641-5611134-45717 WIN2K02          1       1
                     4294500352 Intel(R) Xeon(R) CPU E5-2680 0 @ 2.70GHz 6.1.7601 55041-266-0135507-84842 WIN2008R201      1       1
 
        .LINK            
            Windows Server 2003 incorrectly reports the number of physical multicore processors or hyperthreading-enabled processors. Apply the below hotfix to correct the reported issue. 
            http://support.microsoft.com/kb/932370
 
        .LINK
            Example on retrieving the CPU count. 
            http://www.sql-server-pro.com/physical-cpu-count.html
    #>
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName=$env:COMPUTERNAME
    )
 
    begin {
        $settings = @{
                "Win32_OperatingSystem" = @("Version","SerialNumber")
                "Win32_ComputerSystem" = @({param($result,$output) $output["Capacity"] = $result | Measure-Object -Property Capacity -Sum | Select-Object -ExpandProperty Sum})
                "Win32_Processor" = @(@{n="ProcessorName";e={$_.Name}}, {
                            param($result,$output)
                            $processors = @($result)
                            if ($processors[0].NumberOfCores) {
                                $output["Cores"] = $processors.Count * $processors[0].NumberOfCores
                            } else {
                                $output["Cores"] = $processors.Count
                            }
                            $output["Sockets"] = @($processors | Where-Object {$_.SocketDesignation} | Select-Object -Unique).Count
                        })
        }
    }
 
    process {
        $sessions = $ComputerName | Select-Object @{n="ComputerName";e={$_}} | 
            New-CimSession -SessionOption (New-CimSessionOption -Protocol Dcom)
 
        foreach($session in $sessions) {
            $output = @{}
            foreach($key in $settings.Keys) {
                $result = Get-CimInstance -CimSession $session -ClassName $key
                $output["ComputerName"] = $result.PSComputerName
                foreach($property in $settings[$key]) {
                    if($property -is [string]) {
                        $output[$property] = $result.$property
                    } elseif ($property -is [scriptblock]) {
                        Invoke-Command -ScriptBlock $property -ArgumentList $result, $output
                    } elseif ($property -is [hashtable]) {
                        ($result | Select-Object -Property $property).PSObject.Properties | ForEach-Object {$output[$_.Name] = $_.Value }
                    }
                }
            }
 
            [PSCustomObject]$output
 
            Remove-CimSession -CimSession $session
        }
    }
}

Friday, April 26, 2013

Embedding Csharp in PowerShell Script

I wanted to use a "using block" found in C# to dispose of objects in PowerShell such as Streams or other object types that require the calling of Dispose. After seeing different examples here is where I stopped. I made some minor tweaks.
  1. Begin by creating a class in C#. You can also save the code in a separate file such as Code.cs. See help Add-Type -Examples for additional examples. The class must also inherit from System.IDisposable, otherwise the using-block function will complain with something like 'using-block : Cannot process argument transformation on parameter 'InputObject'. Cannot convert the "Code" value of type "Code" to type "System.IDisposable".'
  2. Create your using block with the instantiation of a new object in parentheses.
  3. Finally, in the script block place your needed logic.

Add-Type @"
using System;

public class Code : IDisposable
{
    public Code()
    {
        Name = "Michael";
    }
    
    public string Name { get; set; }

    public bool IsDisposed { get; set; }
    public void Dispose() 
    {
        Dispose(true);
        GC.SuppressFinalize(this);      
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!IsDisposed)
        {
            IsDisposed = true;   
        }
    }
}
"@

function using-block {
    param (
        [System.IDisposable]$InputObject = $(throw "The parameter -inputObject is required."),
        [ScriptBlock]$ScriptBlock = $(throw "The parameter -scriptBlock is required.")
    )
    try { & $ScriptBlock }
    finally {
        if ($InputObject) {
            if ($InputObject.PSBase) {
                $InputObject.PSBase.Dispose()
            } else {
                $InputObject.Dispose()
            }
        }
    }
}

using-block($c = New-Object Code) {
    $name = $c.Name
    $name # Michael
    $c.IsDisposed # False
}
$name # Not in this scope so no value
$c.IsDisposed # True
This may not be the most elegant approach but it served it's purpose.

Sunday, April 7, 2013

PowerShell Module In Session

I found it a little difficult to bring in functions into a session so I thought this would help others with what helps me get the job done.
The video goes from creating a script module to importing that module into a session.

Tuesday, April 2, 2013

Run with PowerShell Context Menu

Today I was working on our automated build process at work, and found myself running a batch file in a console window that I wanted to remain open. I setup a context menu item associated with .bat files which launches with PowerShell.
Here are the steps:
# We need to create our keys under HKEY_CLASSES_ROOT, which by default has not associated PSDrive.
New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT

# Set the current location to the new drive.
cd HKCR:

# Set the current location to the shell key for .bat files.
cd '.\batfile\shell'

# Create a new key called "Run with PowerShell"
New-Item -Path 'Run with PowerShell'

# Set the current location to the new key.
cd '.\Run with PowerShell'

# Create a new key called "command", which will contain the reference to PowerShell.
New-Item -Path 'command'
cd '.\command'

# Create a new "(default)" string with the command to execute PowerShell. The "%1" contains the path to the .bat file. 
New-ItemProperty -Path '.' -Name '(default)' -Value 'C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe "-nologo" "-noexit" "-command" "& {%1}"'

Here is an example of the output:
PS C:\> New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT

Name           Used (GB)     Free (GB) Provider      Root                  CurrentLocation
----           ---------     --------- --------      ----                  ---------------
HKCR                                   Registry      HKEY_CLASSES_ROOT

PS C:\> cd HKCR:\batfile\shell
PS HKCR:\batfile\shell> New-Item -Path 'Run with PowerShell'

    Hive: HKEY_CLASSES_ROOT\batfile\shell

Name                           Property
----                           --------
Run with PowerShell

PS HKCR:\batfile\shell> cd '.\Run with PowerShell'
PS HKCR:\batfile\shell\Run with PowerShell> New-Item -Path 'command'

    Hive: HKEY_CLASSES_ROOT\batfile\shell\Run with PowerShell

Name                           Property
----                           --------
command

PS HKCR:\batfile\shell\Run with PowerShell> cd '.\command'
PS HKCR:\batfile\shell\Run with PowerShell\command> New-ItemProperty -Path '.' -Name '(default)' -Value 'C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe "-nologo" "-noexit" "-command" "& {%1}"'

(default)    : C:\WINDOWS\SysWow64\WindowsPowerShell\v1.0\powershell.exe "-nologo" "-noexit" "-command" "& {%1}"
PSPath       : Microsoft.PowerShell.Core\Registry::HKEY_CLASSES_ROOT\batfile\shell\Run with PowerShell\command
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_CLASSES_ROOT\batfile\shell\Run with PowerShell
PSChildName  : command
PSDrive      : HKCR
PSProvider   : Microsoft.PowerShell.Core\Registry

Wednesday, March 27, 2013

PowerShell Github Projects

I've been working on a few projects in PowerShell. Here is the source code on Github for those interestered.

PowerShell with GUI in Xaml

PowerShell Deployment

Saturday, March 23, 2013

Discover PowerShell - Episode 5

Introduction on using the splatting feature in Windows PowerShell 3.

Disable PowerShell Remoting

Here are a few quick steps to "undo" the default changes performed by Enable-PSRemoting.
PS C:\> Disable-PSRemoting -Force
WARNING: Disabling the session configurations does not undo all the changes made by the Enable-PSRemoting or Enable-PSSessionConfiguration cmdlet. You might have to manua
lly undo the changes by following these steps:
    1. Stop and disable the WinRM service.
    2. Delete the listener that accepts requests on any IP address.
    3. Disable the firewall exceptions for WS-Management communications.
    4. Restore the value of the LocalAccountTokenFilterPolicy to 0, which restricts remote access to members of the Administrators group on the computer.
PS C:\> winrm delete winrm/config/listener?address=*+transport=HTTP
PS C:\> Stop-Service winrm
PS C:\> Set-Service -Name winrm -StartupType Disabled
PS C:\> Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System -Name LocalAccountTokenFilterPolicy -Value 0 -Type DWord

Discover PowerShell - Episode 4

I've been playing around with PowerShell remoting quite a bit lately and I thought making a tutorial on the basics would help solidify my understanding. Plus, I like teaching so why the heck not?!

Saturday, March 16, 2013

Determine If A Command Exists In PowerShell

Ed Wilson "The Scripting Guy" posted a great article a while back on how to determine if a command exists here. Here is another approach that I came up with that morning.
function Test-Command {
   param($Command)

   $found = $false
   $match = [Regex]::Match($Command, "(?<Verb>[a-z]{3,11})-(?<Noun>[a-z]{3,})", "IgnoreCase")
   if($match.Success) {
       if(Get-Command -Verb $match.Groups["Verb"] -Noun $match.Groups["Noun"]) {
           $found = $true
       }
   }

   $found
}
Here is a breakdown of the regular expression used.
  • The first group in the expression is for the verb, which is 3 to 11 characters long (consult the approved verb list).
  • The second group in the expression is for the noun, which can be 3 or more characters long. I limit the acceptable text to only alphabetical characters and by adding the "IgnoreCase" option we can just use "a-z".
So what would an article be without a quick example.
PS C:\> Test-Command -Command Get-Process
True
PS C:\> Test-Command -Command Get-Proc*
False
Finally, if you would like a shortcut which expects an exact match you can try this:
PS C:\> [bool](Get-Command -Name Get-Process -ea 0)
True

Friday, March 15, 2013

Add Font to PowerShell Console

I saw an article about Adobe's release of a new font called Source Code Pro which was designed in part to help reduce the confusion between certain characters. I thought I would give it a try. After copying the .ttf files to C:\Windows\Fonts, I realized that the font needed to be added in the registry, so here are some steps to get you going.
Change the provider to the registry HKLM: which will get you to HKEY_LOCAL_MACHINE. Navigate to the Console key, then list the properties for TrueTypeFont. Note: I removed some of the extra entries returned by Get-ItemProperty for clarity.
PS C:\> cd HKLM:
PS HKLM:\> cd '.\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console'
PS HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console> Get-ItemProperty -Path TrueTypeFont


0            : Lucida Console
00           : Consolas
PSPath       : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
               NT\CurrentVersion\Console\TrueTypeFont
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
               NT\CurrentVersion\Console
PSChildName  : TrueTypeFont
PSDrive      : HKLM
PSProvider   : Microsoft.PowerShell.Core\Registry
Add the new string value. Here I see that 0 and 00 are already used, so we're going to use 000. Then verify the new string exists.
PS HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console> Set-ItemProperty -Path TrueTypeFont -Name 000 -Value 'Source Code Pro'
PS HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console> Get-ItemProperty -Path TrueTypeFont


0            : Lucida Console
00           : Consolas
000          : Source Code Pro
PSPath       : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
               NT\CurrentVersion\Console\TrueTypeFont
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
               NT\CurrentVersion\Console
PSChildName  : TrueTypeFont
PSDrive      : HKLM
PSProvider   : Microsoft.PowerShell.Core\Registry

Let's try and programmatically set the new font in the console. Create a new PSDrive for the HKEY_USERS hive. Change to the HKU: drive. Change to the SID for the current user. Set the Console property for FaceName to the new font.
PS C:\> New-PSDrive HKU Registry HKEY_USERS

Name           Used (GB)     Free (GB) Provider      Root                                               CurrentLocation
----           ---------     --------- --------      ----                                               ---------------
HKU                                    Registry      HKEY_USERS

PS C:\> cd HKU:
PS HKU:\> cd (New-Object System.Security.Principal.NTAccount($env:USERNAME)).Translate([System.Security.Principal.SecurityIdentifier]).Value
PS HKU:\S-1-5-21-3501008845-2378336731-207489776-1001>
PS HKU:\S-1-5-21-3501008845-2378336731-207489776-1001> Set-ItemProperty -Path Console -Name FaceName -Value 'Source Code Pro'
Now you just need to restart your console and you're good to go!

Saturday, March 9, 2013

Get ValidateSet or Enum Options in PowerShell Command

Edit: I renamed the function because I didn't really like the name.

I've seen a few articles on how to get the valid parameter values for a command, however I thought they were a bit messy. Take for example this:
PS C:\Users\Michael> Set-ExecutionPolicy -ExecutionPolicy WrongValue
Set-ExecutionPolicy : Cannot bind parameter 'ExecutionPolicy'. Cannot convert value "WrongValue" to type
"Microsoft.PowerShell.ExecutionPolicy". Error: "Unable to match the identifier name WrongValue 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 WrongValue
+                                      ~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Set-ExecutionPolicy], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.SetExecutionPolicyCommand

As you can see, you have to sift through the error message to get the valid values. On top of that, you can't do anything with the data because it's trapped in a long string of text. This morning I came up with this function to solve that problem for me.
function Get-ParameterOption {
    param(
        $Command,
        $Parameter
    )

    $parameters = Get-Command -Name $Command | Select-Object -ExpandProperty Parameters
    
    $type = $parameters[$Parameter].ParameterType
    if($type.IsEnum) {
        [System.Enum]::GetNames($type)
    } else {
        $parameters[$Parameter].Attributes.ValidValues
    }
}

Here are some examples of using it. Please note that there really isn't any error checking, that will be in the next version :) This example is for Set-ExecutionPolicy:
PS C:\Users\Michael> Get-ParameterOption -Command Set-ExecutionPolicy -Parameter ExecutionPolicy
Unrestricted
RemoteSigned
AllSigned
Restricted
Default
Bypass
Undefined
This example is for a custom function which uses the ValidateSet attribute.
function Do-Something {
    param(
        [ValidateSet("Low","Medium","High")]
        [string]$Option
    )

    "Something happened"
}

PS C:\Users\Michael> Get-ParameterOption -CommandName Do-Something -Parameter Option
Low
Medium
High
PS C:\> Get-ParameterOption -Command Get-Command -Parameter ErrorAction
SilentlyContinue
Stop
Continue
Inquire
Ignore
After I did a little more research, I guess Jeffrey Snover beat me to it.
Programmatic way to get valid string values for a parameter

Wednesday, February 27, 2013

Discover PowerShell - Episode 3

Final post of the training series tonight. I've started giving these presentations at work and I'm starting feel like I know what I'm talking about. Just a feeling, although it could be the dinner I ate.

Discover PowerShell - Episode 2

I found that making these videos at home is far more relaxing than trying to sit in my cube at work. No strange looks, especially when I restart the recording 5 or 6 times and repeat myself.

Discover PowerShell - Episode 1

I started a training series on using Windows PowerShell 3. The videos closely follow the book by Don Jones, Learn Windows PowerShell 3 in Month of Lunches.

I hope you enjoy them. Feedback always welcome.