PowerShell Cookbook

Search

Categories

 

On this page

Nothing solves everything – PowerShell and other technologies
Accessing Environment Variables in PowerShell
Running PowerShell Scripts from Cmd.exe
Blogging From PowerShell - Editing Posts With the MetaWeblog API
Blogging From PowerShell – Retrieving Posts With the MetaWeblog API
Jim Launches His PowerShell Blog
Abhishek Launches His PowerShell Blog
Score One for Corporate Transparency
The Story Behind the Naming and Location of PowerShell Profiles
Monad Evolves to Windows 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: 216
This Year: 16
This Month: 0
This Week: 0
Comments: 523

Sign In

 Wednesday, May 10, 2006
Thursday, May 11, 2006 6:24:19 AM (Pacific Daylight Time, UTC-07:00) ( )

Raymond recently wrote a post titled, “A new scripting language doesn’t solve everything.”  In that post (and in a previous one,) he points out that backwards compatibility is extremely important for cmd.exe (with itself,) and for PowerShell (with existing cmd.exe scripts.)  More importantly, the point is that PowerShell doesn’t solve everything:

Shipping a new command shell doesn't solve everything either. For one thing, you have to decide if you are going to support classic batch files or not. Maybe you decide that you won't and prefer to force people to rewrite all their batch files into your new language. Good luck on that.

On the other hand, if you decide that you will support batch files after all, then presumably your new command shell will not execute old batch files natively, but rather will defer to CMD.EXE. And there's your problem: You see, batch files have the ability to modify environment variables and have the changes persist beyond the end of the batch file.


Similar threads have come up in the newsgroups in relation to VBScript.

Raymond is absolutely correct, and you won’t find a single person on the PowerShell team that says otherwise.  Or has ever said otherwise, for that matter.  PowerShell is not a swap-in replacement for cmd.exe, it is not a swap-in replacement for the Windows Scripting Host, does not need to re-implement every command-line tool that ships in Windows, and does not solve world hunger.  Tensions that suggest differently are misguided.

Many people – and many systems – depend on the subtle details of the tool with which they’ve chosen to solve their problem.  If you want to write an application that understands the source files for all related technologies (ie: cmd.exe scripts, or .vbs files,) then you’d better make sure that you re-implement every nuance and bug as well.  That includes the bugs that even the owners of those related technologies don’t know about, because I guarantee that a script somewhere depends on it.

The solution isn’t to burn your time by rewriting your systems, either.  If you have code that works – keep it!  I currently have files from 41 different technologies in my “tools” directory:

[D:\lee\tools]
PS:170 > dir | group Extension | sort -desc Count

Count Name                      Group
----- ----                      -----
  275 .exe                      {adlb.exe, ansi2knr.exe, atmar
   59 .ps1                      {#share-file.ps1, backup-chang
   39 .bat                      {articlecounter.bat, c.bat, ch
   25 .dll                      {acctinfo.dll, AutoItX3.dll, B
   20 .cmd                      {d.cmd, devdiv.cmd, dumpfsmos.
   16 .vbs                      {checkrepl.vbs, clean.vbs, clo
    8 .js                       {calc.js, cio.js, d2.js, evt2c
    7                           {%backup%~, 0, _viminfo, autom
    6 .config                   {cordbg.exe.config, perfcompar
    5 .hlp                      {depends.hlp, srvmgr.hlp, usrm
    5 .ini                      {KeePass.ini, memtriage.ini, r
    5 .txt                      {cmgetcer.txt, getcm.txt, inst
    4 .doc                      {kernrate.doc, mqcast.doc, prn
    4 .inf                      {clusfileport.inf, clusfilepor
    3 .reg                      {intfiltr.reg, samplereasons.r
    3 .chm                      {clusterrecovery.chm, eventcom
<snip>

Rewriting for the sake of rewriting provides a poor return on investment – interop is the key word here.  If you want to extend your existing systems, you can do that in the tool of your choice.  Most systems can be extended to include other technologies – a typical Windows build, for example, takes advantage of dozens of different tools.  If you want to write new scripts or systems, you can also do that in the tool of your choice. 

In both cases, PowerShell happens to be a tool that solves certain problems extremely efficiently.

To address one specific detail in Raymond’s post,

“You see, batch files have the ability to modify environment variables and have the changes persist beyond the end of the batch file.”

Try this script.  From a PowerShell.exe window, this wrapper script allows you to run batch files that persistently modify their environment variables.

############################################################################## 
## 
## Invoke-CmdScript.ps1 
## 
## Invoke the specified batch file (and parameters), but also propigate any 
## environment variable changes back to the PowerShell environment that 
## called it. 
## 
## ie: 
## 
## PS > type foo-that-sets-the-FOO-env-variable.cmd 
## @set FOO=%* 
## echo FOO set to %FOO%. 
##  
## PS > $env:FOO 
##  
## PS > Invoke-CmdScript "foo-that-sets-the-FOO-env-variable.cmd" Test
##  
## C:\Temp>echo FOO set to Test. 
## FOO set to Test. 
##  
## PS > $env:FOO 
## Test 
## 
############################################################################## 

param([string] $script, [string] $parameters) 

$tempFile = [IO.Path]::GetTempFileName() 

## Store the output of cmd.exe.  We also ask cmd.exe to output  
## the environment table after the batch file completes 

cmd /c " `"$script`" $parameters && set > `"$tempFile`" "

## Go through the environment variables in the temp file. 
## For each of them, set the variable in our local environment. 
Get-Content $tempFile | Foreach-Object {  
    if($_ -match "^(.*?)=(.*)$"
    {
        Set-Content "env:\$($matches[1])" $matches[2
    }


Remove-Item $tempFile


[Edit: Updated to make more usable for commands with spaces in their names.]

Comments [6] | | # 
Wednesday, May 10, 2006 7:22:20 AM (Pacific Daylight Time, UTC-07:00) ( )

In a recent comment, David asked how to access environment variables in PowerShell.  This is something that we normally just document explicitly: $env:Variable

However, that explicit documentation is actually a shortcut for a feature you may not know about.  It’s a little like dividing fractions – you can do it without ever knowing why it works, just that it works.  Of course, it’s nice to be able to explain why.

As you may know, the ‘$’ character is the way that we access variables in PowerShell.  The probing student might then ask, “Why can we access environment variables the same way we access normal variables.  Isn’t ‘Env:’ the environment provider?  And specific environment variables are children of it?”

Those are good questions.  This is because the Environment provider shares something in common with several other providers – namely support for the *-Content set of core Cmdlets:

[C:\temp]
PS:11 > "hello world" > test

[C:\temp]
PS:12 > get-content test
hello world

[C:\temp]
PS:13 > get-content variable:ErrorActionPreference
Continue

[C:\temp]
PS:14 > get-content function:more
param([string[]]$paths);  if(($paths -ne $null) -and ($paths.length -ne 0))  { ...
       Get-Content $local:file | Out-Host -p    }  }  else { $input | Out-Host ...

[C:\temp]
PS:15 > get-content env:systemroot
C:\WINDOWS

For providers that support the content cmdets, we let you interact with this content through a special variable syntax:

[C:\temp]
PS:16 > $function:more
param([string[]]$paths);  if(($paths -ne $null) -and ($paths.length -ne 0))  { …
       Get-Content $local:file | Out-Host -p    }  }  else { $input | Out-Host …

[C:\temp]
PS:17 > $variable:ErrorActionPreference
Continue

[C:\temp]
PS:18 > $c:test
hello world

[C:\temp]
PS:19 > $env:systemroot
C:\WINDOWS

This variable syntax for content management allows you to both get, and set content:

[C:\temp]
PS:20> $function:more = { $input | less.exe }

[C:\temp]
PS:21> $function:more
$input | less.exe

Now, when it comes to accessing complex provider paths using this method, you’ll quickly run into naming issues:

[C:\temp]
PS:22> $c:\temp\test.txt
Unexpected token '\temp\test.txt' in expression or statement.
At line:1 char:17
+ $c:\temp\test.txt <<<<

The solution to that lies in our escaping support for complex variable names:

[C:\temp]
PS:31 > ${1234123!@#$!@#$12$!@#$@!} = "Crazy Variable!"

[C:\temp]
PS:32 > ${1234123!@#$!@#$12$!@#$@!}
Crazy Variable!

[C:\temp]
PS:33 > dir variable:\1*

Name                           Value
----                           -----
1234123!@#$!@#$12$!@#$@!       Crazy Variable!

… and the content equivalent:

[C:\temp]
PS:34 > ${c:\temp\test.txt}
hello world

So, the way to access environment variables isn’t so unique after all. 

Comments [3] | | # 
 Friday, May 05, 2006
Friday, May 05, 2006 6:34:01 PM (Pacific Daylight Time, UTC-07:00) ( )

One of the things that people often struggle with when they try to use PowerShell scripts as targets of Scheduled Tasks, or launch PowerShell scripts from cmd.exe is the following error message:

C:\Program Files\Windows PowerShell\v1.0>powershell.exe "c:\temp\has space\test.ps1"
'c:\temp\has' is not recognized as a cmdlet, function, operable program, or script file.
At line:1 char:12
+ c:\temp\has  <<<< space\test.ps1

(By the way, you can speed this up by using the -noprofile parameter to powershell.exe)

This is because Powershell doesn’t natively support a parameter for a script to run.  The default command-line argument is “-command,” which defines the command as though you had typed it at the prompt.

For example:

C:\temp\monad>powershell "2+2"
4

This happens to work on script names with no spaces or quotes, as our interpreter interprets that as a command execution.  For scripts with spaces and quotes, you are doing the equivalent of:

[C:\temp]
PS:13 > c:\temp\has space\test.ps1
'c:\temp\has' is not recognized as a cmdlet, function, operable program, or script file.
At line:1 char:12
+ c:\temp\has  <<<< space\test.ps1
 

[C:\temp]
PS:14 > "c:\temp\has space\test.ps1"
c:\temp\has space\test.ps1

Suggestion: Did you mean to run the command in quotes?  If so, try using & "<command>"

So, the solution is:

[C:\temp]
PS:15 > & 'C:\temp\has space\test.ps1'
Hello World

Or,

C:\temp\monad>powershell "& 'c:\temp\has space\test.ps1'"
Hello World

This is something that we know to be a usability issue.  We’re tracking it internally, but a bug (and votes) on Ms Connect would help us prioritize this properly.

Comments [3] | | # 
 Thursday, May 04, 2006
Friday, May 05, 2006 6:09:35 AM (Pacific Daylight Time, UTC-07:00) ( )

In the last post, I mentioned that I wanted to update all of my old Monad posts to include a reference to Windows PowerShell.  That way, users who use the "PowerShell" keyword to search for a topic I've covered (under the Monad codename) can still find it.

I provided a script to get all of the posts from a MetaWeblog-enabled host, so our next goal is to actually make the changes.

To do this, we'll follow the get, modify, and set pattern.  You get the object-based representation of a post (covered in the previous entry,) modify its properties in Monad, and then upload that post back to the web host.  For example:

$blog = .\get-posts.ps1 $endPoint $null "username" "password" 1
$blog.description += "<p>Extra content here</p>"
.\set-blogpost.ps1 $endpoint $null "username" "password" $blog $true

To get the posts originally, we used the MetaWeblog API called "getRecentPosts."  Now, to upload the post back to the web host, we use a different API, called "editPost."  As we did in the previous example, we use .Net to actually talk with the web server.

The XML required for the editPost method only requires 5 things: the Post ID (matching the Post ID we got originally,) a username, password, more XML that describes the post, and a flag that determines if we should publish the post or not.

We could use the Set-BlogPost script to craft the request XML, and also dig around in the $blog object to craft its portion of the XML as well.  But we have an opportunity to introduce a better design here.  In Refactoring parlance, the code has a bad "smell" - namely feature envy.  If your code is overly involved with the methods and properties of another object, then probably that other object should do the work instead.  This is the core of Jeffrey's point: "Whenever you are adding some functions, you should make a conscious decision about whether those functions are best exposed as a "function" or as a "type extension"."

Since we own the blog object (we create it in get-posts.ps1,) we'll have the post XML-ify itself in its ToString() method.

So first, our addition to get-posts.ps1:



         ## Pull the ID of the post
         "postid" { $propertyValue = $property.value.stringbreak }
      }

      ## Add the synthetic property     
      $blogEntry | add-member NoteProperty $propertyName $propertyValue
   } 
-------------8<--------------------------------

   ## Add the ToString method
   ## This method formats the post so that it may be used in an edit
   $blogEntry | add-member -force ScriptMethod ToString { 

      ## A function that encoded our content into an XML-friendly
      ## format
      function encode([string] $xml)
      {
         $tempContent = new-object System.IO.StringWriter
         $textwriter = new-object System.Xml.XmlTextWriter $tempContent
         $textWriter.WriteString($xml)
         $tempContent.ToString()
      }

      @"
      <struct>
        <member>
          <name>dateCreated</name>
          <value>
            <dateTime.iso8601>$($this.dateCreated.ToString("yyyyMMddTHH:mm:ss"))</dateTime.iso8601>
          </value>
        </member>
        <member>
          <name>description</name>
          <value>
            <string>$(encode $this.description)</string>
          </value>
        </member>
        <member>
          <name>title</name>
          <value>
            <string>$(encode $this.title)</string>
          </value>
        </member>
        <member>
          <name>categories</name>
          <value>
            <array>
               <data>
              $(
                  foreach($category in $this.categories)
                  {
                     if($category -and $category.Trim()) { "<value>$category</value>" }
                  }
               )
               </data>
            </array>
          </value>
        </member>
        <member>
          <name>link</name>
          <value>
            <string>$(encode $this.link)</string>
          </value>
        </member>
        <member>
          <name>permalink</name>
          <value>
            <string>$(encode $this.permalink)</string>
          </value>
        </member>
        <member>
          <name>postid</name>
          <value>
            <string>$(encode $this.postid)</string>
          </value>
        </member>
      </struct>
"@
   }
-------------8<--------------------------------

   ## Finally output the object that represents the post
   $blogEntry
}

Then, our script to upload the post:

param([string] $endPoint, [string] $blogid, [string] $username, [string] $password, $blogPost, [bool] $publish = $true)

$postTemplate = @"
<methodCall>
  <methodName>metaWeblog.editPost</methodName>
    <params>
      <param>
        <value>$($blogPost.postId)</value>
      </param>
      <param>
        <value>$username</value>
      </param>
      <param>
        <value>$password</value>
      </param>
      <param>
        <value>
           $blogPost.ToString()
        </value>
      </param>
      <param>
         <value>
            <boolean>$(if($publish) { 1 } else { 0 })</boolean>
         </value>
      </param>
    </params>
</methodCall>
"@

(new-object System.Net.WebClient).UploadString($endPoint, $postTemplate)

And finally, the script to drive it all:

$endpoint = "Endpont Here"
$username = "Username Here"
$password = "Password Here"

$posts = .\get-posts.ps1 $endPoint $null $username $password 9999
foreach($post in $posts)
{
   if(($post.description -match "monad|msh") -and ($post.description -notmatch "powershell"))
   {
      $post.description += "<p>[<i>Edit: Monad has now been renamed to Windows PowerShell.  " + 
         "This script or discussion may require slight adjustments before it applies directly " + 
         "to newer builds.</i>]</p>"
      $post.postid
      .\set-blogpost.ps1 $endpoint $null $username $password $post $true
   }
}

What's unreal is that all of this fancy code is only 89 lines of code when you exclude the XML templates and comments!

[D:\Lee\blogs\Monad]
PS:252 > ((gc get-posts.ps1,set-blogpost.ps1) -notmatch "#|<" | measure-object).Count
89

Comments [0] | | # 
 Wednesday, May 03, 2006
Wednesday, May 03, 2006 7:29:08 AM (Pacific Daylight Time, UTC-07:00) ( )

One of the most difficult aspects of our name change is that we have thousands of pages (spanning hundreds of posts) that use the keyword, “Monad” instead of “PowerShell.”  For users just starting with PowerShell, that means that the content basically does not exist.  If you search for a solution to “PowerShell Hosting,” you find nothing related to Windows PowerShell.

[Tip: If an internet search for PowerShell help comes up fruitless, try it again using Monad as a keyword.  A vast wealth of PowerShell (when it was called Monad) blogging dates back as far back as September, 2004!]

To solve this, I’m going to edit every single one of my PowerShell-related posts to include the following snippet: “[Edit: Monad has now been renamed to Windows PowerShell.  This script or discussion may require slight adjustments before it applies directly to newer builds.]”

I’m not about to do this by hand, of course.  I used the MetaWeblog API to port the content from the old Monad Team Blog to the new PowerShell blog, and was quite pleased with the results.  I may show that one-off script soon, but wanted to give some more detailed explanation of the underlying principles first.

Ideally, I want an experience like this:

$posts = get-blogPosts <parameters>
foreach($post in $posts)
{
   $post.Description += <PowerShell edit string>
   set-post $post
}

So, the first requirement of that scenario leads us to writing a script that produces “BlogPost Objects” – one for each post on the blog.  Here it is:

 

##############################################################################
##
## Get-BlogPosts.ps1
##
## Get the posts to a blog that you own, using the MetaWeblog API
## Returns a strongly-structured set of object that represent the
## posts
##
## Example Usage:
##   $endPoint = "http://www.yourblog.com/blogger.aspx"
##   .\get-posts.ps1 $endPoint $null "username" "password" 3 | ft
##
##############################################################################

param([string] $postUrl, [string] $blogid, [string] $username, 
      [string] $password, [int] $numberOfPosts)


## Post template as required by the metaWeblog.getRecentPosts
## call format
$postTemplate = @"
<methodCall>
  <methodName>metaWeblog.getRecentPosts</methodName>
    <params>
      <param>
        <value>$blogid</value>
      </param>
      <param>
        <value>$username</value>
      </param>
      <param>
        <value>$password</value>
      </param>
      <param>
        <value><i4>$numberOfPosts</i4></value>
      </param>
    </params>
</methodCall>
"@

## Perform the actual post to the server, and transform the response to
## XML
$responseContent = 
   (new-object System.Net.WebClient).UploadString($postUrl, $postTemplate)
$results = [xml] $responseContent

## Go through each of the items in the response to pick out the properties
foreach($item in $results.methodResponse.params.param.value.array.data.value)
{
   ## Prepare our synthetic object
   $blogEntry = new-object System.Management.Automation.PSObject

   ## Go through each of the properties in the current post
   ## For each, compare its property name to one that we know.  From there,
   ## convert the property value into as strongly-typed of a representation
   ## we can muster
   foreach($property in $item.struct.member)
   {
      $propertyName = $property.name
      $propertyValue = $property.value

      switch($propertyName)
      {
         ## The date the post was created.  Uses ISO8601, which is not
         ## natively supported in .Net.  Returned as a [DateTime]
         "dateCreated" 
         { 
            $propertyValue = 
               [DateTime]::ParseExact($property.value."dateTime.iso8601", `
                  "yyyyMMddTHH:mm:ss", `
                  [System.Globalization.CultureInfo]::InvariantCulture)
            break
         }
         
         ## Pull the simple description (content of the post)
         "description" { $propertyValue = $property.value.stringbreak }
         
         ## Pull the title of the post
         "title" { $propertyValue = $property.value.stringbreak }

         ## Pull the categories of the post.  Returned as an array
         ## of strings
         "categories" 
         {
            $propertyValue = @()
            
            foreach($category in $property.value.array.data.value)
            {
               $propertyValue += @($category.string)
            }
 
            break 
         }

         ## Pull the link to the post, returned as an [URI]
         "link" { $propertyValue = [URI] $property.value.stringbreak }

         ## And the permalink to the post, returned as an [URI]
         "permalink" { $propertyValue = [URI] $property.value.stringbreak }

         ## Pull the ID of the post
         "postid" { $propertyValue = $property.value.stringbreak }
      }

      ## Add the synthetic property     
      $blogEntry | add-member NoteProperty $propertyName $propertyValue
   }

   ## Finally output the object that represents the post
   $blogEntry
}


 

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

Comments [1] | | # 
 Monday, May 01, 2006
Tuesday, May 02, 2006 3:34:12 AM (Pacific Daylight Time, UTC-07:00) ( )

It's an interesting world we live in.  As I was checking my Technorati search for "PowerShell," I noticed the first PowerShell blog imposter!  The person starts off, "I'm a PM on the PowerShell team and i'm largely responsible for the PowerShell language ..."

We don't have any Program Managers that blog.

So I click into the blog, with a wry smile on my face -- thinking how much you all will like the story.  Then I realize exactly how much faster the speed of light is than word-of-mouth.

Jim Truher, one of our program managers on the PowerShell team launched his blog today.  Amongst other things, Jim teams up with Jeffrey Snover for many of our well-received conference presentations.

Enjoy!

Comments [0] | | # 
 Sunday, April 30, 2006
Monday, May 01, 2006 5:46:25 AM (Pacific Daylight Time, UTC-07:00) ( )

Abhishek Agrawal, another member of the PowerShell team launched his blog today.  Amongst other things, Abhishek blogged the very cool types.mshxml extension to add MSDN help links to the output of Get-Member.

Enjoy!

Comments [0] | | # 
 Wednesday, April 26, 2006
Thursday, April 27, 2006 6:43:59 AM (Pacific Daylight Time, UTC-07:00) ( )

Knowledge is power – and power is a limited resource.  At least, that’s what some folks believe as they obsessively guard information from others.

That’s a load of tripe.  It creates a work environment so fundamentally backwards: employees direct their efforts toward breaking into the cliques, cabals, and secret societies just to get their jobs done – leaving no time for more important things (like customers.)

There are plenty of examples of this inside of Microsoft.  Rather than give a bad example of information hoarding, let me instead give a powerful example of transparency – the folks behind the Windows Live Mail team.

I had a friend (also a Windows Live Mail customer) mail me yesterday in desperation.  He’d been struggling with an issue in his mail account, and contact with official support channels proved fruitless.  They told him it was a known bug, and would be fixed in the future.  He asked me if I could provide any additional information.

Traditionally, this would result in me contacting a blogger from the product, or sending a mail to their internal discussion alias (if they have one.)  That’s good, but you’re still taxing the resources of the product team.  Self-serve is better.

My first stop was the internal site that lets us search bugs.  Most products publish their bug databases to this site, although many refrain.  So, first tally for transparency – publishing your bugs.  I found a few related to the issue – one with the comment, “this has been turned into a feature, and will be tracked for <milestone x>.”  It included a (broken :)) link to the internal Windows Live Mail SharePoint feature tracking site.

Normally, this would be pretty much the end of the road.  I could contact the last person that made comments in the bug and ask them the question.  That’s good, but you’re still taxing the resources of the product team.  Self-serve is better.

Instead, I was able to go to the feature tracking site and look at what they listed for <milestone x.>  Second tally for transparency: publishing your work items.  I found the work item, and was able to open its full details.  They included the development and PM contacts, and also allowed me to verify that this was the same issue I was looking for.  However, I had no idea when <milestone x> would be live.

Next, I looked at their published master calendar on the same Sharepoint site.  Third tally for transparency: publishing your schedule.  It lists all of the milestone dates, code complete dates, integration dates, release to operations, release to web (live site,) and more.  I simply searched for <milestone x,> and found its release date (which happens to be soon).

So, my final email to the contacts listed in the feature details?

Hi <PM> and <Dev>;

I’ve got a friend who is ... eagerly looking forward to this feature – “<Feature>".  From <feature tracking site>, it looks like the feature is scheduled to ship with <milestone x>. 

From <ship calendar site>, it looks like <milestone x> code goes live on <date>.  Did the feature make it into <milestone x>?

The answer was “yes.” I was able to do nearly all of the groundwork required to become an ambassador for an entirely different team.  Rather than jealously guard the information, the team created value from thin air.  Kudos to the Windows Live Mail team for being so progressive.

Comments [0] | | # 
Wednesday, April 26, 2006 8:18:39 PM (Pacific Daylight Time, UTC-07:00) ( )

“<My Documents>\WindowsPowerShell\Microsoft.PowerShell_profile.ps1.”
and
“<Installation Directory>\Microsoft.PowerShell_profile.ps1.”

It’s probably the 63 characters in PowerShell that have the most thought behind them.  You might ask, “Could it be any longer?  More random?”

Sure!  Other considerations were:

[C:\temp]
PS:55 > $random = new-object Random

[C:\temp]
PS:56 > $output = $null1..100 | % { $output += [char]$random.Next(97122) }; "$output.ps1"
wvrybyxwmtcftvhnsvbednocjrdkcyysjtwnhbkkfrgtamxxdbeckjjjgopivnumtjuaxsgrlpylhucvtegauwhajnhrlbnqwsyp.ps1

[C:\temp]
PS:57 > $output = $null1..100 | % { $output += [char]$random.Next(97122) }; "$output.ps1"
rwyjumqtjxbklsorgtrwbuqklnfjbhonulevewfhfnpllvgslvkcacowgowgggrbinpynsminjqneeypglwewlmswhopaxbxuocx.ps1

[C:\temp]
PS:58 > $output = $null1..100 | % { $output += [char]$random.Next(97122) }; "$output.ps1"
ggfsjbnqlbrbogytjmdvavcfevioirsocbjageuvwvhuomasxgohkfwjeynnryhsidynwynerhxdogpqupqcclevdavgpgexonml.ps1

[C:\temp]
PS:59 > $output = $null1..100 | % { $output += [char]$random.Next(97122) }; "$output.ps1"
mmuyrkodgttwjwschfqvxckwdgnlcynbtdditrjrvboifywsnbqjwpnkbxulvcahfscfskhpuxawnbqeiuxbieddpsgvvxutahdm.ps1


 

:)

Actually, the reason we introduced the $profile variable is to make this as short as possible to you, while still serving our extensibility needs.

So, first off: “My Documents.”

A disconcerting trend is for applications to spew their files all over this directory.  “My Widgets,” “My eBooks,” etc.  They do this even if you’ve never launched the application, or used the given file.  Knowing that, we still ask users to place their profile in their “My Documents.”  This is for two reasons. 

First, we don't generate the files in this directory - they are user-authored documents, not application configuration files.  The folder will also contain other user-authored documents, such as types and formatting customizations.  Even more, the profile is one of a shell user's most treasured documents.  Users quickly consider this to be a vitally personal document - along with their .emacs / .vimrc.  The internet is full of these "dotfiles" that people are proud enough to share.  It is already one of the first and most common things that PowerShell users share. In Word, you generate Word documents.  In Excel, you generate spreadsheets. In PowerShell, you generate profiles, scripts, and ps1xml extension files.  If a user can't point their finger at a file in "My Documents" and remember putting it there, then it's in the wrong spot.

This used to be in a directory called "PsConfiguration" (to be consistent with the installation directory of the All Users' version.)  Since that is no longer where we store the machine-wide profile, this has now changed to a much more understandable "WindowsPowerShell."

Second, the “Application Data” is hidden by default.  That breaks tab completion, discoverability, and so much more for this commonly-used document.

Next: “<Installation Directory>” (for the all-user profiles)

We chose this location because it is the only reasonable and secure location to place a startup script that runs for every user.  If a malicious user can edit this file, they can add commands to it that will elevate that user to Administrator the next time the Administrator logs in (and automatically runs that profile script.) 

We used to store these files in a protected directory under "All Users Documents," but that could still cause security problems.  If the administrator deletes the folder from the “All Users” directory, the Windows ACL lets any user recreate it.  A malicious user could then add an auto-running profile there.

It should really go into an /etc directory, but that oddly seems to be missing in the version of Unix called Windows that I'm currently running :)

Finally, “Microsoft.PowerShell_profile.ps1”

The PowerShell model supports the concept of multiple shells (“hosts”).  For example, the PowerShell.exe console application is the host that most users will know and love.  It has a certain Shell ID.  Karl Prosser's PowerShell Analyzer is another shell.  It has its own Shell ID. In the future, we will have a GUI host that also has its own Shell ID.  You may want code that runs in one of those shells, but not the others. Profile code that accesses $host.UI.RawUI is something that you would probably want to be specific to a shell, for example.

In those situations, you would use the $profile variable. That variable refers to the user-specific profile for the current shell.  Its name derives primarily from the Shell ID of the current shell.  In contrast, the plain "profile.ps1" file is loaded for every shell.

Technically, the PowerShell engine loads profiles in the following order:

  1. "All users" profile is loaded from "<Installation Directory>\profile.ps1"
  2. "All users," host-specific profile is loaded from "<Installation Directory>\Microsoft.PowerShell_profile.ps1"
  3. Current user profile is loaded from "<My Documents>\WindowsPowerShell\profile.ps1"
  4. Current User, host-specific profile is loaded from "<My Documents>\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"

With only one shell (PowerShell.exe,) it doesn't really matter which you use if you use only PowerShell.exe.  I personally use the zenfully correct $profile to hold my modifications, though.

[Edit: Fixed typo in "All Users" document load location.  Thanks, Tony!]
[Edit 10/04/06: Updated to describe the more recent changes in RC2]

Comments [3] | | # 
 Tuesday, April 25, 2006
Tuesday, April 25, 2006 3:53:34 PM (Pacific Daylight Time, UTC-07:00) ( )

Bob Muglia’s Microsoft Management Summit (MMS) keynote this morning unveiled a series of exciting announcements surrounding Monad – now known as Windows PowerShell:

This marks a significant milestone in the Windows PowerShell product lifecycle, and brings us an important step closer to getting a finished product in your hands.

The MMS announcement (and updated beta) will also bring many more users into our community.  If you’re one of them, welcome!  Here are some helpful “getting started” resources (thanks to Bob Wells for collecting these!)


Microsoft TechNet Script Center
===============================
  
http://www.microsoft.com/technet/scriptcenter/default.mspx

   Scripting with the Microsoft Shell
  
http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

Webcasts
========
   Next Generation Command Line Scripting with Monad (Part 1 of 2)
  
http://msevents.microsoft.com/cui/eventdetail.aspx?eventID=1032277850&Culture=en-US

   Next Generation Command Line Scripting with Monad (Part 2 of 2)
  
http://msevents.microsoft.com/cui/eventdetail.aspx?eventID=1032277852&Culture=en-US

Miscellaneous
=============
   Articles:
      "Monad: The Future of Windows Scripting", by Thomas Lee
      TechNet Magazine, November o December 2005, Scripting Column
     
http://www.microsoft.com/technet/technetmag/issues/2005/11/Scripting/default.aspx

      "A guided tour of the Microsoft Command Shell", by Ryan Paul
     
http://arstechnica.com/guides/other/msh.ars

   Blogs:
      PowerShell Team Blog
     
http://blogs.msdn.com/PowerShell/

      Arul Kumaravel's Blog
      http://blogs.msdn.com/ArulK

      Precision Computing: Software Design and Development (Lee Holmes' blog)
     
http://www.leeholmes.com/blog/default.aspx

     
   Books:
      Monad
      Introducing the MSH Command Shell and Language
      by Andy Oakley
      First Edition December 2005
      ISBN: 0-596-10009-4
      206 pages, $34.95 US, $48.95 CA, £24.95 UK
     
http://www.oreilly.com/catalog/msh/

   Newsgroups:
      Microsoft Windows Server Scripting
     
nntp://microsoft.public.windows.server.scripting

   Sample Scripts:
      The Monad Script Centre
     
http://www.reskit.net/monad/samplescripts.htm

   Videos:
      Channel 9 Forums >> The Videos >> Jeffrey Snover >> Monad explained
     
http://channel9.msdn.com/ShowPost.aspx?PostID=25506

      Channel 9 Forums >> The Videos >> Jeffrey Snover >> Monad demonstrated
     
http://channel9.msdn.com/ShowPost.aspx?PostID=25915

      Channel 9 Forums >> The Videos >> Jeffrey Snover >> More talking about Monad
     
http://channel9.msdn.com/Showpost.aspx?postid=127819

   Wikis:
      Wikipedia
     
http://en.wikipedia.org/wiki/MSH_(shell)

      Channel 9 MSH Wiki
     
http://channel9.msdn.com/wiki/default.aspx/Channel9.MSHWiki

   Additional Resource Listings (like this list, only different):
      Monad-MSH
     
http://www.reskit.net/monad

 

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

Comments [5] | | #