PowerShell Cookbook

Twitter Updates

    follow me on Twitter

    Search

    Categories

     

    On this page

    Get-Help –Online
    Things more likely to kill you than Swine Flu
    PowerShell Audio Sequencer
    More Tied Variables in PowerShell
    Want to Influence the PowerShell Cookbook V2?
    Moving and Deleting Really Locked Files in PowerShell
    Making Perfect Change with the Fewest Coins
    More PowerShell Syntax Highlighting
    PowerShell Script Encrypter
    Scripting WinDbg with PowerShell

    Archive

    Blogroll

    Disclaimer
    I work for Microsoft.

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

    RSS 2.0 | Atom 1.0 | CDF

    Send mail to the author(s) E-mail

    Total Posts: 235
    This Year: 12
    This Month: 0
    This Week: 0
    Comments: 634

    Sign In

     Tuesday, June 09, 2009
    Tuesday, June 09, 2009 6:42:33 PM (Pacific Daylight Time, UTC-07:00) ( )

    In CTP2 of PowerShell v2, we added a new parameter to Get-Help: Online. This parameter lets help authors declare an “online version” of their help topic, which PowerShell then launches with your default browser. For the PowerShell cmdlet help topics, we redirect to the excellent (and more frequently updated) online cmdlet help topics: http://technet.microsoft.com/en-us/library/dd347701.aspx.

    PowerShell supports online help content for both MAML-based help topics (http://msdn.microsoft.com/en-us/library/bb525433(VS.85).aspx, http://blogs.msdn.com/powershell/archive/2006/09/14/Draft-Creating-Cmdlet-Help.aspx, http://blogs.msdn.com/powershell/archive/2008/12/24/powershell-v2-external-maml-help.aspx) and inline help comments (http://technet.microsoft.com/en-us/library/dd819489.aspx.)

    To add support for the –Online parameter to your own cmdlet help, add a maml:navigationLink element with a maml:uri node to the maml:relatedLinks section in your MAML help. The help system uses the first navigation link that contains a URI as the target for the link.

    <maml:relatedLinks>
                  <maml:navigationLink>
                         <maml:linkText>Online version:</maml:linkText>
                         <maml:uri>http://go.microsoft.com/fwlink/?LinkID=113279</maml:uri>
                  </maml:navigationLink>
    (…)
    </maml:relatedLinks>

    When writing comment-based help, use the .LINK tag for the same effect:

    001
    002
    003
    004
    005
    006
    007
    008
    ## .SYNOPSIS
    ## Gets an object from the system
    ## .LINK
    ## http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx
    function Get-MyObject
    {
        "Hello!"
    }

    For more information about this feature, see Get-Help Get-Help –Parameter Online.

    Comments [0] | | # 
     Wednesday, April 29, 2009
    Wednesday, April 29, 2009 5:35:41 PM (Pacific Daylight Time, UTC-07:00) ( )

    There are a few. Based on worldwide numbers (160 confirmed deaths, 3000 “suspected” cases)

    • Falling out of bed (900 confirmed deaths)
    • Falling down the stairs (1,690 confirmed deaths)
    • Big storm (874 confirmed deaths)
    • Drinking binge (346 confirmed deaths)

    Of course, the statistic left out by the news

    • Normal flu (36,000 confirmed deaths per year)

    http://www.nsc.org/research/odds.aspx

    Comments [2] | | # 
     Monday, April 20, 2009
    Monday, April 20, 2009 9:53:43 PM (Pacific Daylight Time, UTC-07:00) ( )

    I got forwarded an addictive interactive sequencer yesterday (http://lab.andre-michelle.com/tonematrix) and was immediately hooked. I asked an internal mailing list if there was any kind of hardware that lets you do this kind of thing on the couch, and got the response -- “you mean MIDI?” That’s close, but it is closer to a very simplified sequencer.

    I play classical guitar... even being a fan of electronic music, I had never seen a sequencer used, or tried to make anything in one. I’m sure some researcher out there would love to have me for a “out of touch with reality” anthropology study.

    Then I wondered, “Why should GUI folks have all the fun?”

    88 lines later, a PowerShell Sequencer / Tracker was born: http://www.leeholmes.com/projects/PsTracker/PsTracker.zip. Even as a jaded scripter, I’m constantly amazed how compact PowerShell is. Given an example input:

    # Replace any dash with something else to make a sound in that spot.
    # Format: <NOTE><OCTAVE> <PATTERN>
    # If you restrict yourself to a pentatonic scale (i.e. CDEGAC), anything sounds good.
    # Instruments: # ([Toub.Sound.Midi.GeneralMidiInstruments] | gm -static -mem Property | % { $_.Name } ) -join " "

    # .Instrument OverdrivenGuitar

    C5 ---------OO-OO--
    A5 -------OO-OO---O
    G4 --------------O-
    E4 ----------------
    D4 X---X---X---X---
    C4 ----------------
    A4 ----------------
    G3 ----X-----------
    E3 ----------------
    D3 ----------------
    C3 ----------------
    A3 -------OO-OO----
    G2 ----------------
    E2 ----------------
    D2 ----------------
    C2 ----------------

    # .Instrument SquareLead
    C6 -X-X-XX-X--XXX

    # .Instrument Ocarina
    C7 ---------------X-X-XX-X--XXX

    # .Instrument Kalimba
    C8 -----------------X---X---X---X--

    This is all it takes to process it:

    001
    002
    003
    004
    005
    006
    007
    008
    009
    010
    011
    012
    013
    014
    015
    016
    017
    018
    019
    020
    021
    022
    023
    024
    025
    026
    027
    028
    029
    030
    031
    032
    033
    034
    035
    036
    037
    038
    039
    040
    041
    042
    043
    044
    045
    046
    047
    048
    049
    050
    051
    052
    053
    054
    055
    056
    057
    058
    059
    060
    061
    062
    063
    064
    065
    066
    067
    068
    069
    070
    071
    072
    073
    074
    075
    076
    077
    078
    079
    080
    081
    082
    083
    084
    085
    086
    087
    088
    #requires -Version 2
    param($path, $bpm)

    $scriptPath = & { Split-Path $myInvocation.ScriptName }

    $trackEntries = @{}

    function Update-Track
    {
        $trackEntries.Clear()
        $instrument = $null

        foreach($line in Get-Content $path)
        {
            if($line -match ".*Instrument (.+)([\s]*)$")
            {
                $instrument = $matches[1]
                if(-not $trackEntries[$instrument]) { $trackEntries[$instrument] = @{} }
            }
            elseif($line -notmatch "#|(^[\s]*$)")
            {
                $note,$measures = -split $line
                for($measure = 0; $measure -lt $measures.Length; $measure++)
                {
                    if($measures[$measure] -ne "-")
                    {
                        $trackEntries[$instrument][$measure] = @($trackEntries[$instrument][$measure] + $note)
                    }
                    $trackEntries[$instrument]["Length"] = [Math]::Max($trackEntries[$instrument]["Length"], $measure)
                }
            }
        }
    }


    $fsw = New-Object System.IO.FileSystemWatcher (Split-Path (Resolve-Path $path).ProviderPath),$path
    Register-ObjectEvent $fsw Changed -SourceIdentifier TrackUpdated

    Update-Track

    Add-Type -Path (Join-Path $scriptPath "Toub.Sound.Midi.dll")
    [Toub.Sound.Midi.MidiPlayer]::OpenMidi()

    try
    {
        $sleep = 250
        if($bpm) { $sleep = 1000 * 120 / (8 * $bpm) }

        $currentMeasures = @{}
        while($true)
        {
            $activeNotes = @()
       
            foreach($instrument in $trackEntries.Keys)
            {
                if(-not $currentMeasures[$instrument]) { $currentMeasures[$instrument] = 0 }
                $mappedInstrument = [Toub.Sound.Midi.GeneralMidiInstruments]::$instrument

                [Toub.Sound.Midi.MidiPlayer]::Play(
                    (New-Object Toub.Sound.Midi.ProgramChange 0,0,$mappedInstrument) )
       
                foreach($note in $trackEntries[$instrument][$currentMeasures[$instrument]])
                {
                    [Toub.Sound.Midi.MidiPlayer]::Play( (New-Object Toub.Sound.Midi.NoteOn 0,0,$note,127) )
                    $activeNotes += New-Object Toub.Sound.Midi.NoteOff 0,0,$note,127
                }

                $currentMeasures[$instrument] =
                    ($currentMeasures[$instrument] + 1) % (1 + $trackEntries[$instrument]["Length"])
                   
            }

            Start-Sleep -m $sleep
            $activeNotes | % { [Toub.Sound.Midi.MidiPlayer]::Play($_) }

            if(Get-Event *TrackUpdated*)
            {
                Remove-Event TrackUpdated
                Update-Track
            }
        }
    }
    finally
    {
        [Toub.Sound.Midi.MidiPlayer]::CloseMidi()
        Unregister-Event TrackUpdated
        Remove-Event *TrackUpdated*
    }

     

    For example:

    .\Start-Tracker track.txt 60

    If your system has a MIDI instrument for “Cowbells,” make sure to add more of them! This script builds on Stephen Toub's MIDI library, which I can't seem to find a reference to any longer.

    As an aside, that research junket eventually led me to playing with a more feature-rich (free) sequencer called Linux Multimedia Studio. Keeping with the basis of starting with a pentatonic scale, this took only about an hour or two: http://www.leeholmes.com/projects/PsTracker/strive.mp3.

     

    Comments [0] | | # 
     Thursday, March 26, 2009
    Thursday, March 26, 2009 10:51:52 PM (Pacific Daylight Time, UTC-07:00) ( )

    Ibrahim just posted something to the PowerShell blog about how to create tied variables in PowerShell. If you extend this approach with script blocks, you have a very powerful dynamic scripting technique.

    PS C:\temp> cd \temp
    PS C:\temp> New-ScriptVariable.ps1 GLOBAL:lee { $myTestVariable } { $GLOBAL:myTestVariable = 2 * $args[0] }
    PS C:\temp> $lee
    PS C:\temp> $lee = 10
    PS C:\temp> $lee
    20
    PS C:\temp> New-ScriptVariable.ps1 GLOBAL:today { (Get-Date).DayOfWeek }
    PS C:\temp> $today
    Wednesday
    PS C:\temp> New-ScriptVariable.ps1 GLOBAL:random -Get { Get-Random } -Set { Get-Random -SetSeed $args[0] }
    PS C:\temp> $random
    1740776676
    PS C:\temp> $random
    1507521897
    PS C:\temp> $random = 10
    PS C:\temp> $random
    1613858733
    PS C:\temp> $random = 10
    PS C:\temp> $random
    1613858733

    He alluded to it in the post – here is the full text of the script:

    (Edit 05/17: Updated to make the getters more like PowerShell pipelines: return a single object, or collection of PSObject)

    001
    002
    003
    004
    005
    006
    007
    008
    009
    010
    011
    012
    013
    014
    015
    016
    017
    018
    019
    020
    021
    022
    023
    024
    025
    026
    027
    028
    029
    030
    031
    032
    033
    034
    035
    036
    037
    038
    039
    040
    041
    042
    043
    044
    045
    046
    047
    048
    049
    050
    051
    052
    053
    054
    055
    056
    057
    ## New-ScriptVariable.ps1
    param($name, [ScriptBlock] $getter, [ScriptBlock] $setter)

    Add-Type @"
    using System;
    using System.Collections.ObjectModel;
    using System.Management.Automation;

    namespace Lee.Holmes
    {
        public class PSScriptVariable : PSVariable
        {
            public PSScriptVariable(string name,
                ScriptBlock scriptGetter, ScriptBlock scriptSetter)
                : base(name, null, ScopedItemOptions.AllScope)
            {
                getter = scriptGetter;
                setter = scriptSetter;
            }
            private ScriptBlock getter;
            private ScriptBlock setter;

            public override object Value
            {
                get
                {
                    if(getter != null)
                    {
                        Collection<PSObject> results = getter.Invoke();
                        if(results.Count == 1)
                        {
                            return results[0];
                        }
                        else
                        {
                            PSObject[] returnResults = new PSObject[results.Count];
                            results.CopyTo(returnResults, 0);
                            return returnResults;
                        }
                    }
                    else { return null; }
                }
                set
                {
                    if(setter != null) { setter.Invoke(value); }
                }
            }
        }
    }
    "@


    if(Test-Path variable:\$name)
    {
        Remove-Item variable:\$name -Force
    }
    $executioncontext.SessionState.PSVariable.Set(
        (New-Object Lee.Holmes.PSScriptVariable $name,$getter,$setter))

     

    Comments [5] | | # 
     Wednesday, March 11, 2009
    Thursday, March 12, 2009 5:41:25 AM (Pacific Daylight Time, UTC-07:00) ( )

    We've started working on the next edition of the PowerShell Cookbook, and one obvious goal is to improve on the first version.

    As the first version has been in print, I've taken notes on where people get confused with certain recipes. I've taken notes on what I felt were content gaps, and taken the feedback from reviews on Amazon.com and random blogs. Reviews on Amazon are GOLD for authors. They help readers form educated opinions, and provide helpful feedback about the book itself. If you want to thank the author of a book you like, write a review on Amazon.

    The second edition of the PowerShell Cookbook continues in the same tradition as the first. Topical, real-world solutions to everyday problems. Packed with an appendix of reference material that matters. It will continue to be a purposefully distinct approach from PowerShell in Action.

    With that, here's your chance to influence the next edition. What did you find too basic? Too advanced? Missing altogether? Were there any recurring issues with the approach or content?

    Another question we're pondering is the unique value that the printed edition brings to the table. Much of the content in the PowerShell Cookbook was pre-published to this blog, newsgroups, or other channels. Many of the topics it addresses can be found through internet searches and forums. Many copies are floating around on Bit Torrent. Given all of that, why did you still purchase the printed version?

    I know -- a lot of questions, very few answers! Let 'er rip.

    Comments [6] | | # 
     Tuesday, February 17, 2009
    Tuesday, February 17, 2009 11:55:19 PM (Pacific Standard Time, UTC-08:00) ( )

    Once in awhile, you need to do brain surgery on files locked by the system. This is a common problem run into by patches and hotfixes, so Windows has a special mechanism that lets it move files before any process has the chance to get its grubby little hands on it. This can only be done during a reboot, leading to the dire warning given to you by many installers.

    The Win32 API that enables this is MoveFileEx. Calling this API with the MOVEFILE_DELAY_UNTIL_REBOOT flag tells Windows to move (or delete) your file at the next boot.

    Here’s how to do it from PowerShell:

     

    001
    002
    003
    004
    005
    006
    007
    008
    009
    010
    011
    012
    013
    014
    015
    016
    017
    018
    019
    020
    021
    022
    023
    024

    function Move-LockedFile
    {
        param($path, $destination)

        $path = (Resolve-Path $path).Path
        $destination = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($destination)

        $MOVEFILE_DELAY_UNTIL_REBOOT = 0x00000004

        $memberDefinition = @'
        [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
        public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName,
           int dwFlags);
    '@


        $type = Add-Type -Name MoveFileUtils -MemberDefinition $memberDefinition -PassThru
        $type::MoveFileEx($path, $destination, $MOVEFILE_DELAY_UNTIL_REBOOT)
    }

    [C:\Windows\system32\config\txr]
    PS:181 > dir -force | % { Move-LockedFile $_.Name (Join-Path c:\temp\txr ($_.Name + ".Bak")) }

    [C:\Users\leeholm]
    PS:182 > dir -Filter "NTUser.DAT{*" -force | % { Move-LockedFile $_.Name (Join-Path c:\temp\txr ($_.Name + ".Bak")) }

    Comments [2] | | # 
     Monday, February 09, 2009
    Tuesday, February 10, 2009 5:16:40 AM (Pacific Standard Time, UTC-08:00) ( )

    I've long wondered exactly how few coins you need in your pocket in order to perfectly round out any bill. I usually grab a handful and hope it works out. Even that mathematically astute technique sometimes leaves me a nickel or few pennies short, though, so I settle for making change that gets me a quarter back instead of yet another handful of ore.

    Even this settle-for-second-best option isn't that great. It can cause permanent damage to unsuspecting cashiers that aren't so good at math. Wondering why you would ever give them $1.13 for a $0.88 bill, they'll often just give you your change back AND then the stack of coins they were originally planning to load you up with.

    Well, no longer.

    It turns out that you need exactly 10 coins in your pocket: 3 quarters, 2 dimes, 1 nickel, and 4 pennies. With these in your arsenal, you can make perfect change for any bill.

    How can you be sure? Brute force is your friend, as is PowerShell, of course.

    001
    002
    003
    004
    005
    006
    007
    008
    009
    010
    011
    012
    013
    014
    015
    016
    017
    018
    019
    020
    021
    022
    023
    024
    025
    026
    027
    $coins = @{ 0.25 = 0; 0.10 = 0; 0.05 = 0; 0.01 = 0 }

    function SelectCoins([Decimal] $change)
    {
        $result = $coins.Clone()

        foreach($denomination in $coins.Keys | Sort -Desc)
        {
            while($change -ge $denomination)
            {
                $change -= $denomination
                $result[$denomination]++
            }
        }
       
        $result
    }

    $results = 1..99 | % { SelectCoins ($_ / 100) }

    foreach($denomination in $coins.Keys | Sort -Desc)
    {
        ("{0:c}: " -f $denomination) +
            ($results | % { $_[$denomination] } | 
                Measure-Object -Max).Maximum
    }

     

    Gives:

    $0.25: 3
    $0.10: 2
    $0.05: 1
    $0.01: 4

    Comments [1] | | # 
     Tuesday, February 03, 2009
    Tuesday, February 03, 2009 6:40:56 PM (Pacific Standard Time, UTC-08:00) ( )

    Vladimir Averkin recently wrote a series of posts that show how to export code with syntax highlighting into HTML and RTF formats. It works great in Outlook, but was causing Windows Live Writer to crash. The reason is that the HTML stream of the clipboard isn’t just a blob of HTML – it’s supposed to be placed into the clipboard as CF_HTML. Investigation of that issue gave enough information to exactly pinpoint the crash in Live Writer, which they were quick to resolve once we pointed out. So it was a positive thing after all :)

    While fixing the script, I took the opportunity to make the HTML prettier, work from both the ISE and the command-line, and fix a few bugs. I’ve posted it here: http://www.leeholmes.com/projects/scripts/Set-ClipboardScript.ps1.txt, as well as below.

     

    001
    002
    003
    004
    005
    006
    007
    008
    009
    010
    011
    012
    013
    014
    015
    016
    017
    018
    019
    020
    021
    022
    023
    024
    025
    026
    027
    028
    029
    030
    031
    032
    033
    034
    035
    036
    037
    038
    039
    040
    041
    042
    043
    044
    045
    046
    047
    048
    049
    050
    051
    052
    053
    054
    055
    056
    057
    058
    059
    060
    061
    062
    063
    064
    065
    066
    067
    068
    069
    070
    071
    072
    073
    074
    075
    076
    077
    078
    079
    080
    081
    082
    083
    084
    085
    086
    087
    088
    089
    090
    091
    092
    093
    094
    095
    096
    097
    098
    099
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    ################################################################################
    # Set-ClipboardScript.ps1
    #
    # The script entire contents of the currently selected editor window to system
    # clipboard. The copied data can be pasted into any application that supports
    # pasting in UnicodeText, RTF or HTML format. Text pasted in RTF or HTML
    # format will be colorized.
    #
    # See also:
    # http://blogs.msdn.com/powershell/archive/2009/01/13/
    # how-to-copy-colorized-script-from-powershell-ise.aspx
    # http://www.leeholmes.com/blog/SyntaxHighlightingInPowerShell.aspx
    # http://www.leeholmes.com/blog/RealtimeSyntaxHighlightingInYourPowerShellConsole.aspx
    #
    ################################################################################

    [CmdletBinding()]
    param($path)

    function Get-ScriptName
    {
        $myInvocation.ScriptName
    }

    if($path -and ([Threading.Thread]::CurrentThread.ApartmentState -ne "STA"))
    {
        PowerShell -NoProfile -STA -File (Get-ScriptName) $path
        return
    }

    $tokenColours = @{
        'Attribute' = '#FFADD8E6'
        'Command' = '#FF0000FF'
        'CommandArgument' = '#FF8A2BE2'
        'CommandParameter' = '#FF000080'
        'Comment' = '#FF006400'
        'GroupEnd' = '#FF000000'
        'GroupStart' = '#FF000000'
        'Keyword' = '#FF00008B'
        'LineContinuation' = '#FF000000'
        'LoopLabel' = '#FF00008B'
        'Member' = '#FF000000'
        'NewLine' = '#FF000000'
        'Number' = '#FF800080'
        'Operator' = '#FFA9A9A9'
        'Position' = '#FF000000'
        'StatementSeparator' = '#FF000000'
        'String' = '#FF8B0000'
        'Type' = '#FF008080'
        'Unknown' = '#FF000000'
        'Variable' = '#FFFF4500'
    }

    if($psise)
    {
        $tokenColours = $psise.Options.TokenColors
    }

    Add-Type -Assembly System.Web
    Add-Type -Assembly PresentationCore

    # Create RTF block from text using named console colors.
    function Append-RtfBlock ($block, $tokenColor)
    {
        $colorIndex = $rtfColorMap.$tokenColor
        $block = $block.Replace('\','\\').Replace("`r`n","\cf1\par`r`n")
        $block = $block.Replace("`t",'\tab').Replace('{','\{').Replace('}','\}')
        $null = $rtfBuilder.Append("\cf$colorIndex $block")
    }

    # Generate an HTML span and append it to HTML string builder
    $currentLine = 1
    function Append-HtmlSpan ($block, $tokenColor)
    {
        if (($tokenColor -eq 'NewLine') -or ($tokenColor -eq 'LineContinuation'))
        {
            if($tokenColor -eq 'LineContinuation')
            {
                $null = $codeBuilder.Append('`')
            }
           
            $null = $codeBuilder.Append("<br />`r`n")
            $null = $lineBuilder.Append("{0:000}<BR />" -f $currentLine)
            $SCRIPT:currentLine++
        }
        else
        {
            $block = [System.Web.HttpUtility]::HtmlEncode($block)
            if (-not $block.Trim())
            {
                $block = $block.Replace(' ', '&nbsp;')
            }

            $htmlColor = $tokenColours[$tokenColor].ToString().Replace('#FF', '#')

            if($tokenColor -eq 'String')
            {
                $lines = $block -split "`r`n"
                $block = ""

                $multipleLines = $false
                foreach($line in $lines)
                {
                    if($multipleLines)
                    {
                        $block += "<BR />`r`n"
                       
                        $null = $lineBuilder.Append("{0:000}<BR />" -f $currentLine)
                        $SCRIPT:currentLine++
                    }

                    $newText = $line.TrimStart()
                    $newText = "&nbsp;" * ($line.Length - $newText.Length) + 
                        $newText
                    $block += $newText
                    $multipleLines = $true
                }
            }
       
            $null = $codeBuilder.Append(
                "<span style='color:$htmlColor'>$block</span>")
        }
    }

    function GetHtmlClipboardFormat($html)
    {
        $header = @"
    Version:1.0
    StartHTML:0000000000
    EndHTML:0000000000
    StartFragment:0000000000
    EndFragment:0000000000
    StartSelection:0000000000
    EndSelection:0000000000
    SourceURL:file:///about:blank
    <!DOCTYPE HTML PUBLIC `"-//W3C//DTD HTML 4.0 Transitional//EN`">
    <HTML>
    <HEAD>
    <TITLE>HTML Clipboard</TITLE>
    </HEAD>
    <BODY>
    <!--StartFragment-->
    <DIV style='font-family:Consolas,Lucida Console; font-size:10pt;
        width:750; border:1px solid black; overflow:auto; padding:5px'>
    <TABLE BORDER='0' cellpadding='5' cellspacing='0'>
    <TBODY>
    <TR>
        <TD VALIGN='Top'>
    <DIV style='font-family:Consolas,Lucida Console; font-size:10pt;
        padding:5px; background:#cecece'>
    __LINES__
    </DIV>
        </TD>
        <TD VALIGN='Top' NOWRAP='NOWRAP'>
    <DIV style='font-family:Consolas,Lucida Console; font-size:10pt;
        padding:5px; background:#fcfcfc'>
    __HTML__
    </DIV>
        </TD>
    </TR>
    </TBODY>
    </TABLE>
    </DIV>
    <!--EndFragment-->
    </BODY>
    </HTML>
    "@


        $header = $header.Replace("__LINES__", $lineBuilder.ToString())
        $startFragment = $header.IndexOf("<!--StartFragment-->") +
            "<!--StartFragment-->".Length + 2
        $endFragment = $header.IndexOf("<!--EndFragment-->") +
            $html.Length - "__HTML__".Length
        $startHtml = $header.IndexOf("<!DOCTYPE")
        $endHtml = $header.Length + $html.Length - "__HTML__".Length
        $header = $header -replace "StartHTML:0000000000",
            ("StartHTML:{0:0000000000}" -f $startHtml)
        $header = $header -replace "EndHTML:0000000000",
            ("EndHTML:{0:0000000000}" -f $endHtml)
        $header = $header -replace "StartFragment:0000000000",
            ("StartFragment:{0:0000000000}" -f $startFragment)
        $header = $header -replace "EndFragment:0000000000",
            ("EndFragment:{0:0000000000}" -f $endFragment)
        $header = $header -replace "StartSelection:0000000000",
            ("StartSelection:{0:0000000000}" -f $startFragment)
        $header = $header -replace "EndSelection:0000000000",
            ("EndSelection:{0:0000000000}" -f $endFragment)
        $header = $header.Replace("__HTML__", $html)
       
        Write-Verbose $header
        $header
    }

    function Main
    {
        $text = $null
       
        if($path)
        {
            $text = (Get-Content $path) -join "`r`n"
        }
        else
        {
            if (-not $psise.CurrentOpenedFile)
            {
                Write-Error 'No script is available for copying.'
                return
            }
           
            $text = $psise.CurrentOpenedFile.Editor.Text
        }

        trap { break }

        # Do syntax parsing.
        $errors = $null
        $tokens = [system.management.automation.psparser]::Tokenize($Text,
            [ref] $errors)

        # Initialize HTML builder.
        $codeBuilder = new-object system.text.stringbuilder
        $lineBuilder = new-object system.text.stringbuilder
        $null = $lineBuilder.Append("{0:000}<BR />" -f $currentLine)
        $SCRIPT:currentLine++
      

        # Initialize RTF builder.
        $rtfBuilder = new-object system.text.stringbuilder
       
        # Append RTF header
        $header = "{\rtf1\fbidis\ansi\ansicpg1252\deff0\deflang1033{\fonttbl" +
            "{\f0\fnil\fcharset0 $fontName;}}"
        $null = $rtfBuilder.Append($header)
        $null = $rtfBuilder.Append("`r`n")

        # Append RTF color table which will contain all Powershell console colors.
        $null = $rtfBuilder.Append("{\colortbl ;")
       
        # Generate RTF color definitions for each token type.
        $rtfColorIndex = 1
        $rtfColors = @{}
        $rtfColorMap = @{}
       
        [Enum]::GetNames([System.Management.Automation.PSTokenType]) | % {
            $tokenColor = $tokenColours[$_];
            $rtfColor = "\red$($tokenColor.R)\green$($tokenColor.G)\blue" +
                "$($tokenColor.B);"
            if ($rtfColors.Keys -notcontains $rtfColor)
            {
                $rtfColors.$rtfColor = $rtfColorIndex
                $null = $rtfBuilder.Append($rtfColor)
                $rtfColorMap.$_ = $rtfColorIndex
                $rtfColorIndex ++
            }
            else
            {
                $rtfColorMap.$_ = $rtfColors.$rtfColor
            }
        }
       
        $null = $rtfBuilder.Append('}')
        $null = $rtfBuilder.Append("`r`n")
       
        # Append RTF document settings.
        $null = $rtfBuilder.Append('\viewkind4\uc1\pard\f0\fs20 ')
       
        # Iterate over the tokens and set the colors appropriately.
        $position = 0
        foreach ($token in $tokens)
        {
            if ($position -lt $token.Start)
            {
                $block = $text.Substring($position, ($token.Start - $position))
                $tokenColor = 'Unknown'
                Append-RtfBlock $block $tokenColor
                Append-HtmlSpan $block $tokenColor
            }
           
            $block = $text.Substring($token.Start, $token.Length)
            $tokenColor = $token.Type.ToString()
            Append-RtfBlock $block $tokenColor
            Append-HtmlSpan $block $tokenColor
           
            $position = $token.Start + $token.Length
        }

        # Append RTF ending brace.
        $null = $rtfBuilder.Append('}')
       
        # Copy console screen buffer contents to clipboard in three formats -
        # text, HTML and RTF.
        $dataObject = New-Object Windows.DataObject
        $dataObject.SetText([string]$text, [Windows.TextDataFormat]"UnicodeText")
        $rtf = $rtfBuilder.ToString()
        $dataObject.SetText([string]$rtf, [Windows.TextDataFormat]"Rtf")
        $code = $codeBuilder.ToString()
        $html = GetHtmlClipboardFormat($code)
       
        $dataObject.SetText([string]$html, [Windows.TextDataFormat]"Html")

        [Windows.Clipboard]::SetDataObject($dataObject, $true)
    }

    . Main
    Comments [3] | | # 
     Monday, February 02, 2009
    Monday, February 02, 2009 5:05:46 PM (Pacific Standard Time, UTC-08:00) ( )

    We frequently get questions asking, “Where can I get a PowerShell script encoder so I can write secure scripts like the Visual Basic Script Encoder?”

    The answer is that it is impossible to hide the password from the user if the script ever needs it. This is true of PowerShell, VBScript, C#, C++, Assembly, or any other language. There will always be some point when your script has reversed all of the encryption / protection mechanisms, giving the “attacker” complete access to it. If you don’t want the password itself hanging around in a script file, you can prompt the user for it. If the user is never supposed to know it, then you need to re-think your architecture.

    Microsoft hasn’t been clear enough documenting what protections the Script Encoder offers, but here is an excerpt from the Scripting Guys:

    Now, the important thing to keep in mind is that the script is simply encoded (or obfuscated); it is definitely not encrypted. What does that mean? That means the encoder will hide your script from most people; however, a truly determined hacker - armed with a knowledge of codes or armed with a utility downloaded from the Internet - could crack the code. Among other things, that means that you should never do something like “hide” an Administrator password in a script and assume that the Script Encoder will keep it safe from prying eyes. It won’t. It’s an encoder, not an encrypter, and there’s definitely a difference.

    I’m not sure why the main download page is fond of the term “determined hacker” – a 30 second search for “vbe decryption” returns pages of results.

    Now, a valid response to the whole situation is that you really only want to deter casual investigation, or that reversing the protection can then be linked to a breach of contract or software license. If you are in either of those boats, you don’t need an official tool to do this for you. Hiding your script behind Base64 encoding or ROT-13 should offer plenty of protection, and takes only a few lines of scripting. If you have the skill to make that decision, you have the skill to implement it as well.

    Comments [2] | | # 
     Tuesday, January 20, 2009
    Wednesday, January 21, 2009 1:39:22 AM (Pacific Standard Time, UTC-08:00) ( )

    A while back, Roberto Farah published a script library to help control WinDbg through PowerShell. I’ve been using WinDbg for more debugging lately, and decided (after following one to many object references by hand) that I needed to script my investigations.

    PowerDbg is definitely helpful – Roberto has tons of great scripts published that help analyze all kinds of interesting data. It also made me think of an alternative approach that works around some of the problem areas – PowerDbg uses SendKeys, window focusing, and used WinDbg logging as the communication mechanism. After some investigation, I thought that automation of the command-line version (cdb.exe) through its input and output streams might be easier and more efficient – which indeed it was.

    You set up a remote from the Windbg instance you want to control, and then the WinDbg module (below) connects to the remote session (by manipulating standard in / standard out of cdb.exe) to manage its output.

    Windows PowerShell V2
    Copyright (C) 2008 Microsoft Corporation. All rights reserved.
    PS C:\Users\leeholm> Import-Module Windbg
    PS C:\Users\leeholm> Connect-Windbg "tcp:Port=10456,Server=SERVER"
    PS C:\Users\leeholm> Invoke-WindbgCommand .symfix
    PS C:\Users\leeholm> Invoke-WindbgCommand .reload
    0:009> Reloading current modules
    ................................................................
    ...........
    PS C:\Users\leeholm> Invoke-WindbgCommand k
    0:009> ChildEBP RetAddr
    0460fbe8 76ebd0d0 ntdll!DbgBreakPoint [d:\foo.c @ 206]
    0460fc18 76fc4911 ntdll!DbgUiRemoteBreakin+0x3c [d:\bar.c @ 298]
    0460fc24 76e6e4b6 kernel32!BaseThreadInitThunk+0xe [d:\baz.c @ 66]
    0460fc64 76e6e489 ntdll!__RtlUserThreadStart+0x23 [d:\bing.c @ 2740]
    0460fc7c 00000000 ntdll!_RtlUserThreadStart+0x1b [d:\bing.c @ 2672]
    PS C:\Users\leeholm>

    Here’s an example function to demonstrate its use:

    001
    002
    003
    004
    005
    006
    007
    008
    009
    010
    011
    012
    013
    014
    015
    016
    017
    018
    019
    020
    021
    022
    023
    024
    025
    026
    027
    028
    029
    030
    031
    032
    033
    034
    035
    036
    037
    038
    039
    040
    041
    042
    043
    044
    045
    046
    047
    048
    049
    050
    051
    052
    053
    054
    ################################################################################
    ##
    ## Resolve-PsInvocationInfo.ps1
    ## Resolves the command and script information from an active PowerShell
    ## process
    ##
    ################################################################################
    Import-Module windbg
    Connect-Windbg "tcp:Port=10456,Server=SERVER"

    function Resolve-Member($address, $member)
    {
        $output = Invoke-WindbgCommand !dumpobj $address
       
        if(-not $member)
        {
            $output
        }
        else
        {
            $currentMember = ($member -split "\.")[0]
            $remaining = $member.Remove(0, $currentMember.Length).TrimStart('.')
           
            Write-Verbose "Looking for $currentMember on $address"
           
            $output = $output | 
                Select-String ("(instance|shared).*$currentMember" + '$') |
                Select-String -NotMatch 00000000

            if($output)
            {
                $columns = -split $output
                $address = $columns[6]
               
                Resolve-Member $address $remaining
            }
            else
            {
                Write-Error "Could not resolve $currentMember on $address"
            }
        }
    }

    $null = Invoke-WindbgCommand .loadby sos mscorwks
    $output = Invoke-WindbgCommand ("!dumpheap -type " +
        "System.Management.Automation.InvocationInfo -short")
    foreach($line in $output)
    {
        Resolve-Member $line commandInfo.name
        Resolve-Member $line scriptToken._script
        Resolve-Member $line scriptToken._line
    }

    Disconnect-Windbg

    And the module itself (WinDbg.psm1, place in a “WinDbg” folder in your modules folder):

    (Edit: 05/13/09 - Updated to support local kernel debugging)

    001
    002
    003
    004
    005
    006
    007
    008
    009
    010
    011
    012
    013
    014
    015
    016
    017
    018
    019
    020
    021
    022
    023
    024
    025
    026
    027
    028
    029
    030
    031
    032
    033
    034
    035
    036
    037
    038
    039
    040
    041
    042
    043
    044
    045
    046
    047
    048
    049
    050
    051
    052
    053
    054
    055
    056
    057
    058
    059
    060
    061
    062
    063
    064
    065
    066
    067
    068
    069
    070
    071
    072
    073
    074
    075
    076
    077
    078
    079
    080
    081
    082
    083
    084
    085
    086
    087
    088
    089
    090
    091
    092
    093
    094
    095
    096
    097
    098
    099
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    ################################################################################
    ##
    ## WinDbg.psm1
    ## Automate WinDbg with PowerShell scripting
    ##
    ## To use:
    ## In WinDbg, set up a server: .server tcp:Port=10456
    ##
    ## In PowerShell
    ##
    ## Import-Module windbg
    ## Connect-Windbg -remote "tcp:Port=10456,Server=<server>"
    ## Other remote connection strings / protocols work, too.
    ##
    ## For local kernel debugging:
    ##
    ## Import-Module Windbg -ArgumentList 'C:\<path>\kd.exe'
    ## Connect-Windbg -ArgumentList "-kl"
    ## Invoke-WindbgCommand "!process"
    ##
    ## Cleaning up:
    ## Disconnect-Windbg
    ##
    ################################################################################

    param($cdbPath = "C:\Program Files\Debugging Tools for Windows\cdb.exe")

    $SCRIPT:windbgProcess = $null
    $SCRIPT:currentConnection = $null

    ## Connect to a windbg remote session
    function Connect-Windbg
    {
        param(
            [Parameter(ParameterSetName = "Remote", Mandatory = $true)]
            $remote,

            [Parameter(ParameterSetName = "ArgumentList", Mandatory = $true)]
            $argumentList
            
            )
            
        if($SCRIPT:currentConnection -and ($SCRIPT:currentConnection -ne $remote))
        {
            throw "Already connected to $remote. Use Disconnect-Windbg, " + 
                "then connect to another instance."
        }
        
        ## Launch cdb.exe, the command-line version of WinDbg.
        ## Take control of its input and output streams, which we'll use
        ## to capture commands and their output.
        if(-not $SCRIPT:currentConnection)
        {
            $processStartInfo = New-Object System.Diagnostics.ProcessStartInfo
            $processStartInfo.FileName = $cdbPath
            $processStartInfo.WorkingDirectory = (Get-Location).Path

            if($remote)
            {
                    $processStartInfo.Arguments = "-remote $remote"
            }
            else
            {
                    $processStartInfo.Arguments = $argumentList
            }
            
            $processStartInfo.UseShellExecute = $false
            $processStartInfo.RedirectStandardInput = $true
            $processStartInfo.RedirectStandardOutput = $true 

            $SCRIPT:windbgProcess = 
                [System.Diagnostics.Process]::Start($processStartInfo)
            $SCRIPT:currentConnection = $remote

            if(-not $SCRIPT:windbgProcess)
            {
                return
            }
            
            ## Ignore the stuff that was in the session before we
            ## connected
            $null = Receive-WindbgOutput
        }
    }

    ## Disconnect from the session
    function Disconnect-Windbg
    {
        if($SCRIPT:windbgProcess -and (-not $SCRIPT:windbgProcess.HasExited))
        {
            $SCRIPT:windbgProcess.StandardOutput.Close()
            $SCRIPT:windbgProcess.StandardInput.Close()
            $SCRIPT:windbgProcess.Kill()
        }
        
        $SCRIPT:currentConnection = $null
        $SCRIPT:windbgProcess = $null
    }

    ## Invoke a command in the connected WinDbg session, and return
    ## its output
    function Invoke-WindbgCommand
    {
        if(-not $SCRIPT:windbgProcess)
        {
            throw "Not connected. Use Connect-Windbg to connect to an " +
                "instance of WinDbg."
        }
        
        $SCRIPT:windbgProcess.StandardInput.WriteLine("$args")
        Receive-WindbgOutput
    }

    ## Retrieve pending output from the connected WinDbg session
    function Receive-WindbgOutput
    {
        ## Add a special tag so that we know the end of the command
        ## response
        $sent = "PSWINDBG_COMPLETE_{0:o}" -f [DateTime]::Now        
        $SCRIPT:windbgProcess.StandardInput.WriteLine(".echo $sent")
        
        $received = New-Object System.Text.StringBuilder

        ## Wait for the response to end
        while($received.ToString().IndexOf($sent) -lt 0)
        {
            $null = $SCRIPT:windbgProcess.StandardOutput.EndOfStream
            while($SCRIPT:windbgProcess.StandardOutput.Peek() -ge 0)
            {
                $null = $received.Append(
                    $SCRIPT:windbgProcess.StandardOutput.Read(), 1)
            }
        }
        
        ## Split it into lines, and return everything but the new
        ## prompt, and the "end response" tag
        $output = $received.ToString().Split("`r`n")
        if($output.Length -gt 2)
        {
            $output[0..($output.Length - 3)]
        }
    }

    Export-ModuleMember -Function Connect-Windbg
    Export-ModuleMember -Function Disconnect-Windbg
    Export-ModuleMember -Function Invoke-WindbgCommand

    $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { Disconnect-Windbg }
    Comments [3] | | #