PowerShell Cookbook

Search

Categories

 

On this page

Discovering Registry Settings - PowerShell and Process Monitor
Agony
You Can Write C / Assembly / Perl in any Language
Adding Double-Tap Tab Completion to PowerShell
Introducing: New PowerShell Language Enhancements
How do you get rid of the Judge?
Advanced HTTP / ASP.Net Scripting with PowerShell
Converting C# to PowerShell
Getting Things Done – Outlook Task Automation with PowerShell
Calling a Webservice from 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: 220
This Year: 20
This Month: 0
This Week: 0
Comments: 533

Sign In

 Saturday, May 05, 2007
Saturday, May 05, 2007 10:43:02 PM (Pacific Daylight Time, UTC-07:00) ( )

To give a glimpse into the writing process behind my upcoming "Windows PowerShell - The Definitive Guide" (O'Reilly,) I'll occasionally post entries "as the author sees it." This entry illustrates how to discover registry settings for programs that do not otherwise support scripted automation.

Discover Registry Settings for Programs

Problem

You want to automate the configuration of a program, but that program does not document its registry configuration settings.

Solution

Use Sysinternals' Process Monitor to observe registry access by that program. Process Monitor is available from http://www.microsoft.com/technet/sysinternals/FileAndDisk/processmonitor.mspx.

Discussion

In an ideal world, all programs would fully support command-line administration and configuration through PowerShell cmdlets. Many programs do not, however, so the solution is to look through their documentation in the hope that they list the registry keys and properties that control their settings. While many programs document their registry configuration settings, many still do not.

Although these programs may not document their registry settings, you can usually observe their registry access activity to determine the registry paths they use. To illustrate this, we will use the Sysinternals' Process Monitor to discover PowerShell's execution policy configuration keys. Although PowerShell documents these keys and makes its automated configuration a breeze, it illustrates the general technique.

Launch and configure Process Monitor

Once you've downloaded Process Monitor, the first step is to filter its output to include only the program you are interested in. By default, Process Monitor logs almost all registry and file activity on the system.

First, launch Process Monitor, then press Ctrl-E (or click the magnifying glass icon) to temporarily prevent it from capturing any data. Next, press Ctrl-X (or click the white sheet with an eraser icon) to clear the extra information that it captured automatically. Finally, drag the target icon and drop it on top of the application in question. You can press Ctrl-L (or click the funnel icon) to see the filter that Process Monitor now applies to its output.

Figure 18-2. Process Monitor ready to capture

Prepare to manually set the configuration option

Next, prepare to manually set the program's configuration option. Usually, this means typing and clicking all of the property settings, but just not pressing OK or Apply. For this PowerShell example, type the Set-ExecutionPolicy command line, but do not press Enter.

Figure 18-3. Preparing to apply the configuration option

Tell Process Monitor to begin capturing information

Switch to the Process Monitor window, then press Ctrl-E (or click the magnifying glass icon.) Process Monitor now captures all registry access for the program in question.

Manually set the configuration option

Press OK, Apply, or whatever action it takes to actually complete the program's configuration. For the PowerShell example, this means pressing Enter.

Tell Process Monitor to stop capturing information

Switch again to the Process Monitor window, then press Ctrl-E (or click the magnifying glass icon.) Process Monitor now no longer captures the application's activity.

Review the capture logs for registry modification

The Process Monitor window now shows all registry keys that the application interacted with when it applied its configuration setting.

Press Ctrl-F (or click the binoculars icon), then search for RegSetValue. Process Monitor highlights the first modification to a registry key.

Figure 18-4. Process Monitor's registry access detail

Press Enter (or double-click the highlighted row) to see the details about this specific registry modification. In this example, we can see that PowerShell changed the value of the ExecutionPolicy property (under HKLM:\Software\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell) to RemoteSigned. Press F3 to see the next entry that corresponds to a registry modification.

Automate these registry writes

Now that you know all registry writes that the application performed when it updated its settings, judgment and experimentation will help you determine which modifications actually represent this setting. Since PowerShell only performed one registry write (to a key that very obviously represents the execution policy,) the choice is pretty clear in this example.

Once you've discovered the registry keys, properties, and values that the application uses to store its configuration data, you can use the techniques discussed in @TODO Ref Modify or delete a registry key value to automate these configuration settings. For example:

PS >$key = "HKLM:\Software\Microsoft\PowerShell\1\" +

>>     "ShellIds\Microsoft.PowerShell"

>> 

PS >Set-ItemProperty $key ExecutionPolicy AllSigned

PS >Get-ExecutionPolicy

AllSigned

PS >Set-ItemProperty $key ExecutionPolicy RemoteSigned

PS >Get-ExecutionPolicy

RemoteSigned

Comments [0] | | # 
 Friday, April 27, 2007
Friday, April 27, 2007 3:55:02 PM (Pacific Daylight Time, UTC-07:00) ( )

Comments [1] | | # 
 Wednesday, April 25, 2007
Thursday, April 26, 2007 4:51:17 AM (Pacific Daylight Time, UTC-07:00) ( )

One of the most common tasks when administering a system is working with its files and directories. This is true when you administer the computer at the command line, and is true when you write scripts to administer it automatically.

Fortunately, PowerShell makes scripting files and directories as easy as working at the command line — a point that many seasoned programmers and scripters often miss. A perfect example of this comes when you wrestle with limited disk space, and need to find the files taking up the most space.
A typical programmer might approach this task by writing functions to scan a specific directory of a system. For each file, he or she checks if the file is big enough to care about. If so, they add it to a list. For each directory in the original directory, they repeat this process (until there are no more directories to process.)

As the saying goes, though, "you can write C in any programming language." The habits and preconceptions you bring to a language often directly influence how open you are to advances in that language.

Being an administrative shell, PowerShell directly supports tasks such as "visiting all the files in a subdirectory," or "moving a file from one directory to another." That complicated programmer-oriented script turns into a one-liner:

Get-ChildItem –Recurse | Sort-Object -Descending Length | Select -First 10

Another example came up today in our internal mailing list with the question, "how do list all files NOT in a certain directory?" Another long recursive solution bubbled up in response, but it again turns into a one-liner:

Get-ChildItem –Recurse | Where-Object { $_.FullName –notmatch "\\bin\\" }

Before diving into your favourite programmer's toolkit, check to see what PowerShell supports in that area. In many cases, it can handle it without requiring your programmer's bag of tricks.

(Edit: Changed 'Size' to 'Length' in the example)

Comments [5] | | # 
 Thursday, April 12, 2007
Friday, April 13, 2007 5:51:37 AM (Pacific Daylight Time, UTC-07:00) ( )

One of the gestures that becomes very ingrained when working in a Unix shell is double-tap tab completion. When you press tab at a normal slow speed, the shell cycles through available tab completion possibilities. When you press tab quickly twice in a row, the shell displays all possible completions for your command all at once – and then lets you cycle through those possibilities.

That's not a feature that PowerShell directly supports, but it is possible to get a good approximation to it by customizing your own TabExpansion function. The following script demonstrates a TabExpansion function that illustrates a framework for this. It is terrible as a stand-alone tab completion function (since it only tab completes on filenames – and poorly, at that,) but demonstrates one approach to getting double-tap tab completion in PowerShell.

It comes it two parts:

1) A TabExpansion function, that populates the area just above your prompt with suggestions if you press 'Tab' twice quickly.

function TabExpansion([string] $line, [string] $lastword)
{
    ## Delay for a bit to see if they've pressed a key again
    Start-Sleep -m 200
    if($host.UI.RawUI.KeyAvailable)
    {
        ## Get the list of items to be returned in tab completion. This
        ## is just an example.
        $items = Get-ChildItem $lastWord

        ## Convert those items into a wide string format so that we can display
        ## it concisely
        $content = $items | Format-Wide | Out-String
        $contentLines = $content.Replace("`r","").Split("`n")
        $contentHeight = $contentLines.Length + 2
        $contentWidth = $host.UI.RawUI.BufferSize.Width

        ## If there are more than 100 items (the default in
        ## Unix shells,) it would be courteous to prompt the user to find out if
        ## they actually want to display all of the tab completion information.
        if($contentLines.Length -gt 100)
        {
            return
        }

        ## Create a buffer cell array to hold the string content we plan to
        ## put in the console buffer
        $savedCells = 
        $replacementCells = $host.UI.RawUI.NewBufferCellArray(
            $contentLines,$host.UI.RawUI.ForegroundColor,$host.UI.RawUI.BackgroundColor
            )

        ## Figure out where to put the new bits of buffer
        $coordinates = $host.UI.RawUI.CursorPosition
        $coordinates.Y -= $contentHeight
        $coordinates.X = 0

        ## Bail if we can't fit the replacement content above our prompt
        if($coordinates.Y -le 0)
        {
            return
        }

        ## Create the rectangle that determines which of the old buffer cells
        ## we want to save
        $rectangle = New-Object System.Management.Automation.Host.Rectangle
        $rectangle.Left = 0
        $rectangle.Right = $contentWidth
        $rectangle.Top = $coordinates.Y
        $rectangle.Bottom = $coordinates.Y + $contentHeight

        ## Save the old buffer contents (and their coordinates) into a global
        ## variable so that the next prompt can fix it
        ${GLOBAL:Lee.Holmes.SavedBufferContents} = $host.UI.RawUI.GetBufferContents($rectangle)
        ${GLOBAL:Lee.Holmes.SavedBufferCoordinates} = $coordinates

        ## Put our double-tap tab completion information in an area above the prompt
        $host.UI.RawUI.SetBufferContents($coordinates, $replacementCells)
    }
}

2) A few additional lines in your prompt function to clean up any double-tap output at the next prompt.

    ## Restore tab completion content
    if(Test-Path Variable:\Lee.Holmes.SavedBufferContents)
    {
        $host.UI.RawUI.SetBufferContents(
            ${GLOBAL:Lee.Holmes.SavedBufferCoordinates}, ${GLOBAL:Lee.Holmes.SavedBufferContents}
        )
        ${GLOBAL:Lee.Holmes.SavedBufferContents} = $null
        ${GLOBAL:Lee.Holmes.SavedBufferCoordinates} = $null
    }


 

Comments [5] | | # 
 Sunday, April 01, 2007
Sunday, April 01, 2007 5:17:39 PM (Pacific Daylight Time, UTC-07:00) ( )

First of all, let me thank those of you that have adopted the Ad-Supported version of Get-ChildItem that we introduced last year. The additional revenue has proven quite effective for reducing world hunger, improving world peace, and providing spinner rims to those in need. In fact, several of you did indeed meet fun young DLLs in your area. As just one example of many, Jeffrey Snover recently announced his engagement to shell32.dll! Congratulations guys!

Anyways, back to the topic at hand. One thing we've sometimes heard is that the PowerShell scripting language is too complex – the dizzying array of cmdlets, control statements, and keywords is enough to make many shy away.

We've been listening, and have quietly been developing a significant language enhancement package to eliminate this complexity.

Take, for example, just SOME of the different ways to do a "count down" in PowerShell

dir $ENV:WINDIR\System32\*.dll | % { $_.Length % 11 } | Sort -Unique -Descending

$counter = 10; while($counter -ge 1) { $counter; $counter = $counter - 1 }

for($counter = 10; $counter -ge 1; $counter--) { $counter }

foreach($item in 10..1) { $item }

10..1 | Foreach-Object { $_ }

10..1 | Foreach { $_ }

10..1 | % { $_ }

10..1

Right there, you have EIGHT opportunities to make a mistake. Who needs all of those options, when all you want is to count down from 10 to 1?

As a solution to this conundrum, we've developed a new subset of the language that supports only the minimal statements required to make a program. Working in this language is like working at a conveyor belt, or with the tape in a cassette tape. You can:

  • Move the tape left
  • Move the tape  right
  • Look at what's in front of you on the tape
  • Compare what's in front of you with something you already know
  • Put something new in front of you on the tape

That's it. Using these techniques alone, you can develop any software that can be written for a computer. Think of some fun applications!

  • Rocket guidance software
  • A Microsoft Windows emulator
  • A new object-based, conveyor-belt-based shell!

Just to give you an example of how simple and intuitive this language is, here is a program that counts from 25 to 1:

[C:\temp]
PS:28 > . c:\temp\KBB04012007.ps1

[C:\temp]
PS:29 >    $actions = @(
>>    ##    Current State         See  Put  Move     New State      Debug Output
>>       ,@("initial",            "1", "1", "Right", "scan_right_work",       "")
>>       ,@("initial",            "0", "0", "Right", "scan_right_no_work",    "")
>>       ,@("scan_right_no_work", "0", "0", "Right", "scan_right_no_work",    "")
>>       ,@("scan_right_no_work", "1", "1", "Right", "scan_right_work",       "")
>>       ,@("scan_right_no_work", " ", " ", "Right", "done",                  "")
>>       ,@("scan_right_work",    "0", "0", "Right", "scan_right_work",       "")
>>       ,@("scan_right_work",    "1", "1", "Right", "scan_right_work",       "")
>>       ,@("scan_right_work",    " ", " ", "Left",  "at_end",                "")
>>       ,@("at_end",             "1", "0", "Left",  "reset",                 "")
>>       ,@("at_end",             "0", "1", "Left",  "scan_left",             "")
>>       ,@("scan_left",          "0", "1", "Left",  "scan_left",             "")
>>       ,@("scan_left",          "1", "0", "Left",  "reset",                 "")
>>       ,@("reset",              "1", "1", "Left",  "reset",                 "")
>>       ,@("reset",              "0", "0", "Left",  "reset",                 "")
>>       ,@("reset",              " ", " ", "Right", "initial", "Next Number ^^")
>>    )
>>

[C:\temp]
PS:30 >    Initialize "11001"

[C:\temp]
PS:31 >    "Initial tape: $tape"
Initial tape: 11001

[C:\temp]
PS:32 >    RunProgram $actions

[C:\temp]
PS:33 >    "Final tape:   $tape"
Final tape:    00000

Enjoy. Attached is a pre-release version of KBB04012007 that will be released next patch Tuesday.

KBB04012007.ps1.txt (26.18 KB)

[Edit: Alas, another change not meant to be.  Perhaps in version 2.  Happy April Fools' :)]

Comments [4] | | # 
 Tuesday, March 27, 2007
Wednesday, March 28, 2007 2:15:38 AM (Pacific Daylight Time, UTC-07:00) ( )

Wow. I was looking through some old backups today, and found a writing assignment where we actually had to turn in the drek that came out when working to clear "The Judge."

The assignment was to do 8 of these 20-minute sessions in one week. By the end, I was closing my eyes and making an insane amount of typos.

Time 1:40pm, September 17.  Word count 1405
Time 1:26am, September 19.  Word count 1409
Time 11:46pm, September 19.  Word count 1694
Time 4:41pm, September 20.  Word count 1730
Time 6:59pm, September 21.  Word count 1793
Time 11:15pm, September 22.  Word count 1856
Time 11:08pm, September 23.  Word count 1942
Time 9:30apm, September 24.  Word count 1879
Time 11:35a, September 30.  Word count 1715

And to be clear that we're not talking about poetry here:

Time 11:35a, September 30.  Word count 1715

 

Well it=s time again..  I=ll stop typing at 11:55 If the cassiopeia ahasn=t gone off..  This is interesting talking for awhile... It=s not that exciting, but htat=s OK.  What i will be interested in ins is ws seeing how fo t it makes my craetivity better hen it comes to going for a boat fir de ride and seeing how aI am doing?  I think I will be doing OK, but I haven=t typed in awhile , so I don=t know what I am feeling like.  I know I am feeling pretty well, so that=s OK and I don=t have to worry about it.  I=m kinda sad that I ddin=t eever h it the 2000 word b mark, but it will come in time.  I just ahve eto remember to ekekeep on tyalking fast and then that will be oK OK.  That=s mint..  That=s really mint, and t I=m mind all about that.  Hello how are you doing?  I am doing fine, but I don=t really know what to talk about..  The funmny think thing is that bp people were talking about how they would say A I don= tknow what to talk about so I=ll just write about it@... I can=t even imagine having so much time alone with my mind that I would think that way?  I think that this is loosening up my braein fluid dthough, so OH my GOD that was the Judge coming in again.  I thi though t I had given him the bood bot... I thhink I might as well hae removed my BS cas key that is backspace not bullshit..  Hahahah... I might as well have removed it, buec becaseu becasu because I ahd eliminated the problem of wanting to backspace at all... Hello that= s really strange.  I wish bubba would stop playing witht the TB TV because it=s really annoying..  And then we just let e her and she walkas walks around think in th thinking about it... That=s poretty mn nuts but if wh she want=s to she can do it.  Hello I think that=s carzy, but I don=t know crazy all I know is f nutty funiness..  Fuziness i s is not funiness, that is not funny.. .H ello how are yo udougn??  I think I we will describe the funniness of the thinkint and the hello and the hello and how are you i you?  I don= thtink th you are all that well, ebcause I have 15 minutes left and I am giong as as slow as one could imagine... I think that was pretty nutes ant that I was 20 go asdf I don=t want to think about that.  What I really want to think about is the deep inner space fof the human psyche and all of that jazz..  I think that I have to return the SparQ a lot sooner than I really want to... I really want to, so hello I think I will ..  Hello that=s really nuts.  I have to blow my nose too, but ydo you hear me complaining about it?  I think that she=s cout caught her... I think she=s been tricked, but I don=t think r she really minsd..  I think the neat thing about ebaing a man of letters and a man of email is that I keep up my typing shk skills, and I keep up my literary skills.  I don= twrite t letters s themselves very often , but that=s OK..  I don=t think I reallly want to... Hello that=s pretty nuts.  Hello I don=t know what I to do..  I think that alltheweb.com indexes 20 million to Lycos=s on ly only has 7..  I don=t kknow what to think about..  I think I if I was the man.  i of all men, I would index onlly the title... I t don=t know what kind of crap I would get in searches, though, because when you have title s titles that purposely do that, and mess up tht tite title hthatn I would bre breally annoyed that I would come up with thi... Oanother option would be to j I dont= know I... I think you would have to have a nutty nutinees,,,..  I think that the web should be ... D See thent the think thing is, that msost people don=t have an idea what their page is about, so the title isn=t ev ery descriptive... I think that is nuts... I On the ohter hadn, you could just so go searhc searching for informations and keep a adatabase of nuts and peanuts especially..  I don=t htink I would mind that at all..  I think I would like a peanut butter and jam sandwich today..  I think I would also like some of the nutella..  Hello how are you doing?  I tink think I am doing fine!  I think that fr Frankie are lying.  Hello howa rey oug?  I think that I am doing fine, but I don=t htink I am anhywhere even near 1500 words... I think If i am w to do this, it will be more like 14000 1400 1300 words or so..  I odjust don=t have tht the speed that I did nbe before, and I j certainly don= thta have the speed of my fingers..  I don=t even know how fast I could bring it up if c I continued to type at all the speed int the o workl world for the next 10 minmutes..  I f I continued to type at 100 wpm for then enxt while, as compare t compared to the 0 l words I was typing before, then w that would take me to 85 words per mintes..  However that i swhat I am ocom always thyping at, so I think that I am only able to makx max out I think that I fi type really cast and don=t care about anything else then I will end up at about some hshee... I think that I will end up ab at about 15600 ow words, seeing as I am not raelly going all that fast and that I am not raelliy going as fast ags sas gI can..  I think that >s mint..  That candle smells really goodd... I think that I love vanilla..  I am actually in lvoe with the flavour aof fa vanilla..  I don=t knowo what in the heck is going on..  I have to work with cr cChris and Somebboyd eles to day..  That=s rpetty OK..  A Aand I don= tmind it at all..  I am fine about coming early , alnthough I don= th tink I will have to..  I think I shall just go to sleep now and let myu fingers to d do t he walking..  Walk walk walk hahaha..  That=s rn not really that funny..  I think it shall realkly go to sleep..  I don=t know how I will be able to write and that I don= tknow what to say!  I tihn Think that I was want to go for about a page at a font 1 of about 13 14 15 so that I will probably havbe to write around 5 600 words... I am not paln planning an anthology a or and epoch or anything like that..  I just hope to write and write..  as fasrt as I can ..  That makes me happy..  I think that I am happy that I am happy that I am watching TV and typing at the same it time..  I th ink that w I will be doing about 1500 words, in retrospect..  That makes me tired... actually I would really not br be very typed typed..  O What is a typed language?  Hello how are you doing?  Hello I don=t know wwhat to do?  I think that I am tired and would reatud gradually be following int obeing nice?  Hello how are you dongt?  I think that I wam am adoing fine?/ I think that they are fighting.  Hello how are you ding?  I don=t know what to do?  I am very angry angry with th her that is really carea crazy ?  Hello how are you dong?  I am nuts..  I think that she is fighting and that is no fun?  I think that I saw another another argument..  Everybody fights won once ain awhile..  I think that the interesting thing is that I can just type and type.;..  Without even considering what is giog around on the computer??  I( think that I thingk it is interesting that I can watch tv and talk at the same times..  I think that my fingers have managed to go on cone consider how my fingers..  I thin that that the funny thing aobut the funinees..  Hello I don= tknow what I=m typing abut aat least a I am wathcving TV..  IT=s important that I am watching TV..  I think I will be fininished at about 11:55, but I don= tknow why the Cassiopeia hasn=t gone of..  I think the problem i sthat I se it about aon only a mintue or so beco before I startes d started looking at the TV tco cmoptuer..  So I se t ts et the computer, and then thing king thtat the Casiopeia was talkinga longerthan it wo was, I didn=t really care..  That=s OK ..  I can=t believe that i >m aon on 15 pages..  That=s re pretyt crazy..  My finger types fast aenought..  I love her too,.  I think that it must be a alittl ech.. . OK I have just a seocnd sleft..  That=s wahat I though..  It didn=t take me long enought to go from the cassiopeia to the comptuer.  I was expecting a lot less time.. .OH OK anyways I w think that I will be wrapping it up, buecause I am almost done..  Almost sI tell you..  I o wonder if that should be a hint..  I have it save on me, but it is only every 20 minutes... That actually is it sa..  BVTE 

 

Comments [0] | | # 
 Monday, March 26, 2007
Monday, March 26, 2007 10:07:35 PM (Pacific Daylight Time, UTC-07:00) ( )

Back in the good ol’ days, my domain used to support a catch-all email address. I could give out any email address (as long as it ended in @leeholmes.com,) and still be guaranteed to receive the mail. When asked to create an account at some random website, I just make one up on the spot tailored specifically to them.

I got an odd look from my dentist when I did this, though. They must have thought — “who in their right mind likes dentists so much that they pick it for their email address?” As with all computer jokes, explaining it didn’t make it any better – I just got a smile and nod.

In any case, my domain soon fell under the wrath of a dictionary attack spammer – which I noticed when I was getting spam as fast as Outlook could receive it. I quickly disabled my catch-all alias, but I immediately missed how easy it was to create a new temporary alias. Creating the aliases manually got old quickly, so I wrote a script to do it instead. It works against “SmarterMail Professional Edition” – the software used at WebHost4Life (the hosting company I use.)

If you use WebHost4Life, feel free to use this script to let you create email aliases with ease. If you don’t, you might still find the code useful for building your own script to automate HTTP / ASP.Net scenarios.

PS >New-MailAlias foobar2

cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:
Credential
User: domain-admin-address
@leeholmes.com
Password for user domain-admin-address@leeholmes.com: ***********

Logging in.
Logged in.
Creating alias.
Alias created.

(Note: This script uses Send-TcpRequest, which I posted earlier as “Connect-Computer”: http://www.leeholmes.com/blog/ScriptingNetworkTCPConnectionsInPowerShell.aspx)

##############################################################################
##
## New-MailAlias.ps1
##
## Create a new mail alias on a mail host managed by a "SmarterMail 
## Professional Edition" web interface
##
## The alias provided will redirect mail to the user specified by $recipient
## The credential should be the full email address and password of the domain
## email administrator -- usually postmaster@$mailHost.
##
##############################################################################

param(
    [string] $alias = $("Specify an alias"), 
    [System.Management.Automation.PsCredential] $cred = $(Get-Credential))

[void] [Reflection.Assembly]::LoadWithPartialName("System.Web")

$mailHost = "mail.example.com"
$recipient = "me@example.com"

function Main
{
    Write-Host "Logging in."
    $session = LogIn
    Write-Host "Logged in."

    Write-Host "Creating alias."
    $viewstate = GetAliasPage $session

    SubmitNewAlias $viewstate $session
    Write-Host "Alias created."
}

function LogIn
{
    ## The template for the login request
    $loginRequest = 
@"
GET /Login.aspx HTTP/1.1
Host: $mailHost


"@

    ## Request the login page from the server
    $loginPage = Send-TcpRequest $mailHost 80 -Input $loginRequest

    ## Extract the ASP.Net session ID and viewstate for the page
    $session = GetSessionId $loginPage
    Write-Verbose "Got session: $session"
    $viewstate = GetViewState $loginPage

    ## The template for the login page submission
    $login = 
@"
POST /Login.aspx HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: __LENGTH
Host: $mailHost
Cookie: SelectedLanguage=; permcookie=1680x1050; ASP.NET_SessionId=__SESSION; screensize=1680x1050; settings=

__POSTDATA


"@

    ## Add the username, password, and viewstate into the information we want to
    ## post
    $postData = "__EVENTTARGET=&__EVENTARGUMENT=&__VIEWSTATE=__REPLACEMENTVIEWSTATE&txtUserName=__USERNAME&txtPassword=__PASSWORD&LanguageList=&btnEnterClick.x=0&btnEnterClick.y=0"
    $postData = $postData.Replace("__REPLACEMENTVIEWSTATE", [System.Web.HttpUtility]::UrlEncode($viewstate))
    $postData = $postData.Replace("__USERNAME", [System.Web.HttpUtility]::UrlEncode($cred.GetNetworkCredential().UserName + '@' + $cred.GetNetworkCredential().Domain))
    $postData = $postData.Replace("__PASSWORD", [System.Web.HttpUtility]::UrlEncode($cred.GetNetworkCredential().Password))

    ## Replace the placeholders for the post data in the login template, as well as
    ## the ASP.Net session cookie
    $login = $login.Replace("__LENGTH"$postData.Length)
    $login = $login.Replace("__POSTDATA"$postData)
    $login = $login.Replace("__SESSION"$session)

    ## Submit the login page, and check for errors
    $output = Send-TcpRequest $mailHost 80 -Input $login
    if($output -match "frmerror.aspx")
    {
        Write-Error "Failed to log in."
        Write-Error "Sent:"
        Write-Error $login
        Write-Error "Output:"
        Write-Error $output

        exit
    }
    ##############################################################################

    $session
}

function GetAliasPage($session)
{
    ## The template for the alias page request
    $aliasRequest = 
@"
GET /DomainAdmin/frmUserAlias.aspx HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)
Host: $mailHost
Cookie: SelectedLanguage=; permcookie=1680x1050; ASP.NET_SessionId=__SESSION; screensize=1680x1050; settings=empty


"@

    ## Replace the placeholder for the ASP.Net session cookie
    $aliasRequest = $aliasRequest.Replace("__SESSION"$session)

    ## Request the alias page from the server
    $aliasPage = Send-TcpRequest $mailHost 80 -Input $aliasRequest

    ## Check for errors
    if($aliasPage -match "frmerror.aspx")
    {
        Write-Error "Failed to get alias page."
        Write-Error "Sent:"
        Write-Error $aliasRequest
        Write-Error "Got:"
        Write-Error $aliasPage

        exit
    }

    ## Extract the viewstate from the page
    GetViewState $aliasPage
}

function SubmitNewAlias($viewstate$session)
{
    ## The template for the alias page submission
    $addAlias = 
@"
POST /DomainAdmin/frmUserAlias.aspx HTTP/1.1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)
Content-Length: __LENGTH
Host: $mailHost
Cookie: SelectedLanguage=; permcookie=1680x1050; ASP.NET_SessionId=__SESSION; screensize=1680x1050; settings=empty

__POSTDATA


"@

    ## Add the alias name and viewstate into the information we want to post
    $postData = "__EVENTTARGET=SaveTextImageButton&__EVENTARGUMENT=&__VIEWSTATE=__REPLACEMENTVIEWSTATE&headerinnercontrol_folderlist1_TreeView1_CheckedList=&headerinnercontrol_folderlist1_TreeView1_MultipleSelectedList=&sbsearchtext232=&sbsearchfolder232=&sbsearchfield232=0&headerinnercontrol_pulldownmenu1_M1_ContextData=&headerinnercontrol_pulldownmenu1_M2_ContextData=&txtName=__ALIASNAME&txtEmails=$recipient"
    $postData = $postData.Replace("__REPLACEMENTVIEWSTATE", [System.Web.HttpUtility]::UrlEncode($viewState))
    $postData = $postData.Replace("__ALIASNAME", [System.Web.HttpUtility]::UrlEncode($alias))

    ## Replace the placeholders for the post data in the login template, as well as
    ## the ASP.Net session cookie
    $addAlias = $addAlias.Replace("__LENGTH"$postData.Length)
    $addAlias = $addAlias.Replace("__POSTDATA"$postData)
    $addAlias = $addAlias.Replace("__SESSION"$session)

    ## Submit the alias page, and check for errors
    $output = Send-TcpRequest $mailHost 80 -Input $addAlias

    if($output -match "tiptextfailure")
    {
        Write-Error "Failed to add alias."
        $output = $output -replace "(?s).*<div class='tiptextfailure'>([^<]+)</div>.*",'$1'
        Write-Error $output

        exit
    }
    elseif($output -match "frmerror.aspx")
    {
        Write-Error "Failed to add alias -- protocol error"
        Write-Error "Sent:"
        Write-Error $addAlias
        Write-Error "Got:"
        Write-Error $output

        exit
    }
    elseif($output -notmatch "</html>")
    {
        Write-Error "Failed to add alias -- confirmation not present"
        Write-Error "Sent:"
        Write-Error $addAlias
        Write-Error "Got:"
        Write-Error $output

        exit
    }
}

## Extract the ASP.Net session ID frcom a page
function GetSessionId($page)
{
    $page -replace '(?s).*Set-Cookie: ASP.NET_SessionId=([^;]+);.*','$1'
}

## Extract the viewstate from a page
function GetViewState($page)
{
    $page -replace '(?s).*name="__VIEWSTATE" value="([^"]+)".*','$1'
}

. Main

 

Comments [1] | | # 
 Monday, March 19, 2007
Monday, March 19, 2007 5:19:57 PM (Pacific Daylight Time, UTC-07:00) ( )

Tom recently ran into a problem with the Connect-WebService script given here -- when run against a server that returns malformed headers, he received the error message, “The server committed a protocol violation.

The .NET Framework takes this approach as a security precaution, since parsing non-standard headers is one of the most common sources of vulnerabilities. It’s not that the header-parsing code has vulnerabilities (it may or may not, I have no inside knowledge,) but it is an absolute fact that you increase your risk as you expose more code to malicious input.

If you connect to a malicious server without this safeguard, then, you run an increased risk that the server might use malformed headers to exploit problems in the code intended to deal with it.

If you trust the server, then you might want work with it in all of its malformed-header glory, anyways. According to Tom’s research on the Internet, though, the only way to programmatically do this is through an internal property on the System.Net.Configuration.SettingsSection class.

The accepted solution from the MSDN Forum looks like this:

public static bool SetAllowUnsafeHeaderParsing20()
{
  //Get the assembly that contains the internal class
  Assembly aNetAssembly = Assembly.GetAssembly(typeof(System.Net.Configuration.SettingsSection));
  if (aNetAssembly != null)
  {
    //Use the assembly in order to get the internal type for the internal class
    Type aSettingsType = aNetAssembly.GetType("System.Net.Configuration.SettingsSectionInternal");
    if (aSettingsType != null)
    {
      //Use the internal static property to get an instance of the internal settings class.
      //If the static instance isn't created allready the property will create it for us.
      object anInstance = aSettingsType.InvokeMember("Section",
        BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.NonPublic, null, null, new object[] { });
      if (anInstance != null)
      {
        //Locate the private bool field that tells the framework is unsafe header parsing should be allowed or not
        FieldInfo aUseUnsafeHeaderParsing = aSettingsType.GetField("useUnsafeHeaderParsing", BindingFlags.NonPublic | BindingFlags.Instance);
        if (aUseUnsafeHeaderParsing != null)
        {
          aUseUnsafeHeaderParsing.SetValue(anInstance, true);
          return true;
        }
      }
    }
  }
  return false;
}


Which translates cleanly to PowerShell as this:

$netAssembly = [Reflection.Assembly]::GetAssembly([System.Net.Configuration.SettingsSection])

if($netAssembly)
{
    $bindingFlags = [Reflection.BindingFlags] "Static,GetProperty,NonPublic"
    $settingsType = $netAssembly.GetType("System.Net.Configuration.SettingsSectionInternal")

    $instance = $settingsType.InvokeMember("Section", $bindingFlags, $null, $null, @())

    if($instance)
    {
        $bindingFlags = "NonPublic","Instance"
        $useUnsafeHeaderParsingField = $settingsType.GetField("useUnsafeHeaderParsing", $bindingFlags)

        if($useUnsafeHeaderParsingField)
        {
          $useUnsafeHeaderParsingField.SetValue($instance, $true)
        }
    }
}
Comments [0] | | # 
 Wednesday, February 28, 2007
Thursday, March 01, 2007 5:17:09 AM (Pacific Standard Time, UTC-08:00) ( )

Scott just finished writing about his boss (and now him) using Blat to help him Get Things Done.

I've been "Getting Things Done" for some time, and one thing that's always annoyed me was how difficult it is to convert a desire into a categorized task. Outlook 2007 had the opportunity to make this better with the quick-entry field in the TODO bar, but tasks entered that way unfortunately have a due date for "Tomorrow." While not terrible, it means additional fiddling around – which I wanted to avoid in the first place.

The blat solution is helpful, but still requires an intermediary. Once the reminder is in your inbox, it requires an additional step of triaging to convert it into a task.

Some time back, I wrote the following script to make it easy to directly enter tasks into Outlook from PowerShell. It even validates the category (if you specify one,) so you don't get items in the wrong category:

## Add-OutlookTask.ps1
## Add a task to the Outlook Tasks list

param( $description = $(throw "Please specify a description"), $category, [switch$force )

## Create our Outlook and housekeeping variables. 
## Note: If you don't have the Outlook wrappers, you'll need
## the commented-out constants instead

$olTaskItem = "olTaskItem"
$olFolderTasks = "olFolderTasks"

#$olTaskItem = 3
#$olFolderTasks = 13

$outlook = New-Object -Com Outlook.Application
$task = $outlook.Application.CreateItem($olTaskItem)
$hasError = $false


## Assign the subject
$task.Subject = $description

## If they specify a category, then assign it as well.
if($category)
{
    if(-not $force)
    {
        ## Check that it matches one of their already-existing categories, but only
        ## if they haven't specified the -Force parameter.
        $mapi = $outlook.GetNamespace("MAPI")
        $items = $mapi.GetDefaultFolder($olFolderTasks).Items
        $uniqueCategories = $items | Sort -Unique Categories | % { $_.Categories }
        if($uniqueCategories -notcontains $category)
        {
            $OFS = ", "
            $errorMessage = "Category $category does not exist. Valid categories are: $uniqueCategories. " +
                            "Specify the -Force parameter to override this message in the future."
            Write-Error $errorMessage
            $hasError = $true
        }
    }

    $task.Categories = $category 
}

## Save the item if this didn't cause an error, and clean up.
if(-not $hasError) { $task.Save() }
$outlook = $null


 

One valid point that Scott brought up for using a batch file instead of a PowerShell script is that it was easier to run batch files from places like Start | Run, SlickRun, and apps like that. One thing I sometimes do is write a wrapper batch file:

REM todo.bat
powershell -command "Add-OutlookTask '%1'
'@TODO' -Force"

Another option is to associate PowerShell as the interpreter for .PS1 files. Which reminds me...

[C:\Temp]
PS:44 > Add-OutlookTask
"Write about associating PowerShell with PS1" "@Write"

 

Comments [3] | | # 
 Tuesday, February 27, 2007
Wednesday, February 28, 2007 5:17:41 AM (Pacific Standard Time, UTC-08:00) ( )

One question that comes up fairly frequently in our internal mailing list, the newsgroup, and the internet at large is how to call a webservice from PowerShell. In fact, several excellent PowerShellers have written about it: Keith, and Geert. In general, the guidance has been to use wsdl.exe to generate the webservice proxy, compile that proxy into a DLL, then finally load that DLL into memory.

This is a topic that I cover in my upcoming book, and initially wrote a script to automate these proxy generation steps. However, the prerequisite to running a script designed in that matter is fairly huge. Wsdl.e