## Archive for the ‘Powershell’ Category

### How to uninstall Powershell v1.0 from Windows Server 2003 R2

Monday, April 27th, 2009

If youâ€™re lucky, you can uninstall Powershell simply by finding the Windows update in ARP that has the signature blue icon.  When I was rolling out Powershell CTP3 (now deeply integrated into our build & deploy scripts), this worked on the majority of our machines.  All except the TFS server, as luck would have it.  Figures!

According to KB926140, this will fail if you installed Powershell before you installed SP2 (or upgraded to R2) because the latter update includes the former.  MSI patching is fun like that.  So if you want v2.0 goodness, itâ€™s time to do some surgery.

WARNING: this is completely unsupported by Microsoft.  It happened to work for me.  YMMV.

First you need to dig up a copy of PSCustomSetup.exe.  If you have a machine that already has PS v1.0 on it, youâ€™ll find this tool in %windir%\$NtUninstallKB926139$.  (Note: the KB number may be 928439 on Vista, but I donâ€™t have a Vista machine to say for sure.  Use your â€œgci -filterâ€ skillz.)  Or you can dig it out of the v1.0 install package.  You might also try to grab a copy of spuninst.inf thatâ€™s appropriate for your OS, just to sanity check you arenâ€™t missing anything, though we will be hacking our ownâ€¦

Basically, we need to manually replay the steps that the uninstaller would have taken.  Ordinarily, the .inf file spells this out in a vaguely human-readable format.  Thanks to the efforts of some folks around the net[1][2][3], I was able to come up with a comprehensive list.  Technically it can vary from OS to OS, or even machine to machine, but you wouldnâ€™t be here unless you had the 2003 R2 issue in particular â€“ XP and Vista arenâ€™t affected since PS is not part of their service packs.  Plus, itâ€™s fairly easy to abstract out the machine-specific paths with an environment variable here & there.  Thus, my instructions:

1) Copy the PSCustomSetup.exe you found to a temporary directory on the affected machine.

2) Copy/paste this first script block into a file named uninstall.cmd and save it into the temporary directory.

pushd %windir%\Microsoft.NET\Framework\v2.0.50727
ngen.exe uninstall "System.Management.Automation,Version=1.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35,ProcessorArchitecture=msil" /silent /nologo /NoDependencies
ngen.exe uninstall "Microsoft.PowerShell.ConsoleHost,Version=1.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35,ProcessorArchitecture=msil" /silent /nologo /NoDependencies
ngen.exe uninstall "Microsoft.PowerShell.Commands.Management,Version=1.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35,ProcessorArchitecture=msil" /silent /nologo /NoDependencies
ngen.exe uninstall "Microsoft.PowerShell.Commands.Utility,Version=1.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35,ProcessorArchitecture=msil" /silent /nologo /NoDependencies
ngen.exe uninstall "Microsoft.PowerShell.Security,Version=1.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35,ProcessorArchitecture=msil" /silent /nologo /NoDependencies
ngen.exe uninstall "System.Management.Automation.resources,Version=1.0.0.0,Culture=en,PublicKeyToken=31bf3856ad364e35,ProcessorArchitecture=msil" /silent /nologo /NoDependencies
ngen.exe uninstall "Microsoft.PowerShell.ConsoleHost.resources,Version=1.0.0.0,Culture=en,PublicKeyToken=31bf3856ad364e35,ProcessorArchitecture=msil" /silent /nologo /NoDependencies
ngen.exe uninstall "Microsoft.PowerShell.Commands.Management.resources,Version=1.0.0.0,Culture=en,PublicKeyToken=31bf3856ad364e35,ProcessorArchitecture=msil" /silent /nologo /NoDependencies
ngen.exe uninstall "Microsoft.PowerShell.Commands.Utility.resources,Version=1.0.0.0,Culture=en,PublicKeyToken=31bf3856ad364e35,ProcessorArchitecture=msil" /silent /nologo /NoDependencies
ngen.exe uninstall "Microsoft.PowerShell.Security.resources,Version=1.0.0.0,Culture=en,PublicKeyToken=31bf3856ad364e35,ProcessorArchitecture=msil" /silent /nologo /NoDependencies
popd

PSCustomSetupUtil.exe /removeenvvariable PATH "C:\WINDOWS\system32\WindowsPowerShell\v1.0"
PSCustomSetupUtil.exe /removeenvvariable PATHEXT ".PSC1"
PSCustomSetupUtil.exe /wmsettingchange

rd /s /q %windir%\$NtUninstallKB926139$
rd /s /q %windir%\system32\windowspowershell

pushd %windir%\assembly\gac_msil
rd /s /q System.Management.Automation
rd /s /q Microsoft.PowerShell.ConsoleHost
rd /s /q Microsoft.PowerShell.Commands.Management
rd /s /q Microsoft.PowerShell.Commands.Utility
rd /s /q Microsoft.PowerShell.Security
rd /s /q System.Management.Automation.resources
rd /s /q Microsoft.PowerShell.ConsoleHost.resources
rd /s /q Microsoft.PowerShell.Commands.Management.resources
rd /s /q Microsoft.PowerShell.Commands.Utility.resources
rd /s /q Microsoft.PowerShell.Security.resources
popd

3) Open a CMD window, navigate to the temporary directory, and run the script.  Watch for errors.  (Post to the commentsâ€¦)  Make sure itâ€™s a 64-bit CMD window if youâ€™re on x64 Windows.

4) Copy this block of text to a file named uninstall.reg and save it to the temporary directory.  Run it and accept the â€œyes, I really want to do thisâ€ prompt.

Windows Registry Editor Version 5.00

[-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell]
[-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\OID\EncodingType 0\CryptSIPDllCreateIndirectData\{603BCC1F-4B59-4E08-B724-D2C6297EF351}]
[-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\OID\EncodingType 0\CryptSIPDllGetSignedDataMsg\{603BCC1F-4B59-4E08-B724-D2C6297EF351}]
[-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\OID\EncodingType 0\CryptSIPDllIsMyFileType2\{603BCC1F-4B59-4E08-B724-D2C6297EF351}]
[-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\OID\EncodingType 0\CryptSIPDllPutSignedDataMsg\{603BCC1F-4B59-4E08-B724-D2C6297EF351}]
[-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\OID\EncodingType 0\CryptSIPDllRemoveSignedDataMsg\{603BCC1F-4B59-4E08-B724-D2C6297EF351}]
[-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\OID\EncodingType 0\CryptSIPDllVerifyIndirectData\{603BCC1F-4B59-4E08-B724-D2C6297EF351}]
[-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\HotFix\KB926139]
[-HKEY_CLASSES_ROOT\.ps1]
[-HKEY_CLASSES_ROOT\.ps1xml]
[-HKEY_CLASSES_ROOT\.psc1]
[-HKEY_CLASSES_ROOT\Microsoft.PowerShellConsole.1]
[-HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1]
[-HKEY_CLASSES_ROOT\Microsoft.PowerShellXmlData.1] 

5) Install Powershell v2.0

6) Clean up the temp dir.

Success?  Hope so â€“ if not, itâ€™s probably time to nuke that install!  (or at minimum, restore from backup)  Good luck everyone.

Note: these instructions do not remove shortcuts from the Start Menu or anywhere else.  Youâ€™ll need to do that yourself.

### Get-TfsItemProperty is the bridge between server and local objects

Friday, March 27th, 2009

Most of the real-world examples of the TFS powershell tool revolve around querying (and possibly updating) the state of the server.

And rightfully so: most of the operations you want to do locally are already possible without any special tools.  The â€œPowershell snap-in systemâ€ we know and love today was created to help IT admins manage their servers â€“ even today, only a brave few developers have completely replaced cmd.exe at their daily workstation.

Nevertheless, we shouldnâ€™t be satisfied to have our TFS cmdlets live in their own little world.  Luckily, all the info we could ever want about a TFS version controlled item â€“ including its local state â€“ comes packaged in the ExtendedItem object.  Even more fortuitously, if you revisit the exploration of QualifiedItemSpec with an eye for detail, youâ€™ll notice that one overload of ToQualifiedItem() is not like the others.*  In the constructor where ExtendedItem decays into QualifiedItem, the properties saved into the resulting tuple include LocalItem and VersionLocal instead of more familiar ones like ServerItem.  This is not a coincidence :)

Letâ€™s sum up what we know so far.  Native TFS objects are funneled down the pipeline by stripping them to bare essentials about the items they represent (QIs); these serve as a common interface between cmdlets.  When an ExtendedItem is stripped down, the next cmdlet will use the local filename & local version info as its parameters.  Get-TfsItemProperty is the cmdlet that returns ExtendedItems.  Capiche?

Time to put our newfound knowledge into action.  If youâ€™ve also been following the last several posts on file manipulation in powershell, these examples will hopefully inspire an â€œaha!â€ moment.

 001 002 003 004 005 # find all C# makefiles with HintPath elements, check them out, and open them in my text editor tfprop $/project/*csproj -r | get-fullpath | ss hintpath | tfedit -passthru | ue# find my shelvesets related to powershell, unshelve them, and open every file in the ISE get-tfsshelveset | ? {$_.name -like "*powershell*" } | tfunshelve | tfprop | ise

[Note that I have get-tfsitemproperty aliased to â€˜tfpropâ€™ rather than â€˜tfpropertiesâ€™ as it appears in a default install of the Power Tools.]

Are we having fun yet?!

So far weâ€™ve only really exercised the filename part of the QI.  Equally interesting things happen when you take advantage of the special ability that Get-TfsItemProperty offers to manipulate the local version.

 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 # Force unshelve to pend changes against your current workspace ("have") version. Ordinarily, # unshelve rolls the affected files back to the version you had at the time you shelved the # changes. # Pro: this ensures your workspace stays in a consistent, buildable state # Con: you may need to resolve content and/or namespace conflicts function unpack {     [CmdletBinding()]     param (         [parameter(Mandatory=$True, Position=0, ValueFromPipeline=$True)]         [Microsoft.TeamFoundation.PowerTools.Powershell.ShelvesetSpec]$ss ) process {$currentWorkspaceVersions = tfstatus -shelveset $ss | tfprop restore-tfsshelveset$ss | out-null         $currentWorkspaceVersions | tfget tf resolve /prompt } } [Microsofties: think bbpack / jjpack] Opens up a whole new world of TFVC scripting mania, wouldnâ€™t ya say? *Iâ€™ll admit the one for GettingEventArgs is a little funky as well. Here, the choice of properties to expose came down to UX / gut feel. TargetLocalItem is usually what you want to see; it tends to be the â€œfinal resultâ€ of mainline operations, surviving things like Rename and Undo fairly well. But itâ€™s not always the best choice. Obviously when itâ€™s null we need a backup; good old ServerItem will do. Itâ€™s [almost always] guaranteed to be present, but itâ€™s not always perfect either â€“ now you have the occasional strange appearance of server paths in your list of local files being touched by the Update. Finding the right info gets really tricky really fast: was TargetLocalItem null because of a pending Delete, because itâ€™s cloaked or otherwise excluded from our workspace, or because weâ€™re undoing a pending Branch or Merge? Thatâ€™s just one example. The logic behind which name tf.exe displays in which scenario is actually one of the more complex pieces of the app. With powershell we can accept â€œgood enoughâ€ since the object model lets you manipulate things to your heartâ€™s content. ### Powershell tidbit: hit F6 in the ISE to execute the current selection Friday, March 27th, 2009 For anyone who hasnâ€™t discovered this feature yet, make sure to give it a whirl. Itâ€™s by far my favorite â€œlittleâ€ thing about the new environment. /Berg tweet out ### Powershell tidbit: hacky way to find the 32-bit Program Files directory Friday, March 27th, 2009 In the process of tidying up another function for posting, I found that there is no clean way to get a Powershell variable from the system that: 1. Points to â€œC:\program files (x86)â€ regardless whether youâ€™re in a 32-bit or 64-bit process. 2. Exists on legacy 32-bit operating systems. Sounds like a common thing youâ€™d need: to find the location where 32-bit programs are installed regardless of any other factors. Powershellâ€™s$env:ProgramFiles(x86) variable comes close, but even if it were present on 32-bit OSes -- honestly not sure about that -- the parentheses in the variable name present a nasty parse problem.  No amount of quoting or backtick`ing or $()â€™ing got me the result I was looking for. If you have more patience than me, have at it. And oh yeah, did I mention I was shopping for a better null coalescing operator than the one that comes with PSCX? Note to PSCX folks: I hate wrapping stuff in scriptblocks when thereâ€™s no real need for them. Thanks, Richardâ€™s pinky finger. So anyway, I resorted to putting this abomination in my$profile:

 001 002 003 004 function ?? {     (@($args | ?{$_}) + $null)[0] } set-variable programFilesX86 (?? (cat 'Env:\ProgramFiles(x86)' -ea SilentlyContinue)$Env:ProgramFiles) -scope global

Yes, itâ€™s ugly and nonstandard (in the sense that I canâ€™t rely on this being present in other peopleâ€™s script environments)â€¦suggestions welcome...

### Invoke-UltraEdit.ps1: integrate your favorite text editor with the Powershell pipeline

Friday, March 27th, 2009

Less talk, more code.  As long as that code has ample inline comments, 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 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 <# .Synopsis Opens the desired file(s) in UltraEdit.      .Description     The file(s) you specify on the command line or the pipeline will be opened in UltraEdit. We'll launch a new     copy of the app, but only if necessary. The pipeline supports many varieties of objects via my Get-FullPath     script.          Put this in your $profile: PS> . "c:\path\to\invoke-ultraedit.ps1" PS> set-alias ue invoke-ultraedit .Parameter File Path to the file being opened. Any object that contains such a path as a property named FullName, Path, etc. will do. .Example PS> # open foo.txt in UltraEdit PS> ue foo.txt .Example PS> # open any project files that contain a HintPath element PS> dir -r -fil *csproj | ss hintpath | ue .ReturnValue.Link http://richardberg.net/blog/?p=60 http://richardberg.net/blog/?p=57 .Notes NAME: Invoke-UltraEdit.ps1 AUTHOR: COATUECAP\rberg LASTEDIT: 03/27/2009 17:24:02#Requires -Version 2.0 #> function Invoke-UltraEdit { [CmdletBinding()] Param( [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)] [psobject]$file     )       Begin     {         # this path isn't stored anywhere in the registry - you may need to adjust it         $ue = join-path$programFilesX86 "IDM Computer Solutions\UltraEdit\Uedit32.exe"                   # If UltraEdit isn't running yet, launching it will hang the console until it exits         # Same deal if there's an elevated UltraEdit process running but our shell is not elevated         Function HaveToStartNewEditor         {            # If there is at least one process whose handle we have permission to grab, we're ok.             $haveToStart =$true             get-process (dir $ue).basename -ea SilentlyContinue | % { if ($_.Handle -ne $null) {$haveToStart = $false } } return$haveToStart         }     }        Process     {         while (HaveToStartNewEditor)         {              invoke-item $ue sleep 2 } # launch with option to use existing window &$ue /foi ($file | get-fullpath) } } As you can probably tell, youâ€™ll need some other code for this to compilebe interpreted successfully. ### Easily manage files in Powershell ISE CTP3 Friday, March 27th, 2009 Since Win7 debuted, Iâ€™ve started forcing myself to use the ISE more often. It doesnâ€™t have a ton of features â€“ even the much awaited debugger lags some v1.0-era 3rd party IDEs â€“ but it does show great potential. In particular, who doesnâ€™t love an environment thatâ€™s scriptable using its native language? :) [and oh yeah, we finally get to leave the world of CSRSS.exe behind. Ctrl+V! sweet blessed Ctrl+V!] Naturally, I started the customization process by scouring the web. Keithâ€™s "Yank Lineâ€ function quickly joined my Custom menu. (though I map it to Ctrl+K, a vestige of muscle memory from Pico. thanks for the memories, crappy Solaris boxes at Duke!) Soon after came a function templater from Andy Schneider, handy especially since the new v2 syntax is pretty verbose. And of course Leeâ€™s syntax highlighter, without which the pretty code youâ€™re seeing would not be possible â€“ though in full disclosure Iâ€™ve heavily modified it. (first to fit into a Module, then to remove the Apartment Threading overhead thatâ€™s thankfully not relevant in the ISE, and finally to take the currently selected text instead of the whole document) Any other recommendations for$profile inclusion?  Or know how to override a keyboard shortcut, for that matter?

Back to the point of this post.  My work style tends to involve dozens of tabs, files scattered all over the place, saved & not, until I clean things up hours later.  So naturally the built-in file management features of ISE have got to go!  With the help of the $psise object model, the get-fullpath script I introduced last time, and liberal additions to the ISE profile (Microsoft.PowerShellISE_profile.ps1), itâ€™s not hard to greatly beef things up: • quick Open from the interactive prompt, including rich pipeline support seen in previous posts • ditto for Close • Close All But This • Undo Close with a full FIFO stack of everything youâ€™ve ever closed using my functions • Save All • Copy Full Path to clipboard  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 # quick open from the command prompt. Pipeline enabled :) function ise { [CmdletBinding()] param ( [parameter(Mandatory=$True, Position=0, ValueFromPipeline=$True)] [psobject]$file,         [Parameter()]         [switch] $PassThru =$false      )        process      {         if ($PassThru) {$file }                $psise.CurrentOpenedRunspace.OpenedFiles.Add(($file | get-fullpath))     } }# ditto for close, with my desired keyboard shortcuts function close {     [CmdletBinding()]     param (         [parameter(Position=0, ValueFromPipeline=$True) [psobject]$file = ""     )         begin     {         # getting desired behavior of "no input = close current file" is harder than it looks...         $closedSomething =$false                 # set up global undo buffer         if ($RecentlyClosedTabs -eq$null)             { set-variable -name RecentlyClosedTabs -value (new-object system.collections.stack) -scope Global }     }         process     {         if ($file -ne "") # thought I could use the 'continue' statement instead of a giant If block, but it exits the whole function {$fullPath = $file | get-fullpath if (@($fullPath).count -gt 0)  # can't simply check against null for some weird reason             {                 # because there is no constructor for System.Management.Automation.Host.OpenedFile, I have to do stupid iteration                 # that makes this routine n^2                 $fileToClose =$psise.CurrentOpenedRunspace.OpenedFiles |                      ? { $_.fullpath -eq$fullpath }                                    $psise.CurrentOpenedRunspace.OpenedFiles.Remove($fileToClose) | out-null                 $closedSomething =$true                 $RecentlyClosedTabs.Push($fullPath)             }         }     }         end     {         if (! $closedSomething) {$curFile = $psise.CurrentOpenedFile$psise.CurrentOpenedRunspace.OpenedFiles.Remove($curFile) | out-null$RecentlyClosedTabs.Push($curFile) } # after we've closed a file, chances are we don't want the cursor stuck in another random file$psise.CurrentOpenedRunspace.CommandPane.Focus()     } } # $psISE.CustomMenu.Submenus.Add('_Close File', {close}, 'Ctrl+W') # can't overwrite keyboard shortcuts :(function CloseAllButThis { # need to clone the array of opened files since we'll be modifying it as we enumerate$temp = [array]::createinstance([System.Management.Automation.Host.OpenedFile], $psise.CurrentOpenedRunspace.OpenedFiles.Count)$psise.CurrentOpenedRunspace.OpenedFiles.CopyTo($temp, 0) # edge case: if there's only 1 file open, do nothing if ($temp.count -eq 1)         { break }       # close the files     $temp | ? {$_ -ne $psise.CurrentOpenedFile } | close }$psISE.CustomMenu.Submenus.Add('Close All But _This', {CloseAllButThis}, 'Ctrl+Shift+W')# this will spew errors when there are Untitled files, but I kinda like that behavior function SaveAll {        $psise.CurrentOpenedRunspace.OpenedFiles | % {$_.Save() } } $psISE.CustomMenu.Submenus.Add('_Save All', {CloseAllButThis}, 'Ctrl+Alt+S') # really want Ctrl+Shift+S :(# undo close tab(s) function UndoClose ([int]$files = 1) {     if ($RecentlyClosedTabs -eq$null -or $RecentlyClosedTabs.Count -le 0 -or$files -le 0)         { break }         # if user tries to pop more items than we have on the stack, just let the error bubble up     while($files -gt 0) {$RecentlyClosedTabs.Pop() | ise         --$files } }$psISE.CustomMenu.Submenus.Add('_Undo Close', {UndoClose}, 'Ctrl+Alt+U')# copy full path of the open file to the clipboard function Get-OpenedPath {     $psise.CurrentOpenedFile.FullPath | out-clipboard }$psISE.CustomMenu.Submenus.Add('Copy Full _Path', {Get-OpenedPath}, 'Ctrl+Alt+C')

As you can see, the Close function ended up much uglier than expected.  Powershell limitations/bugs or programmer ignorance?  Suggestions welcome.  Powershell verb-noun Nazis need not apply.  Functions which live only in the $profile for quick consumption at the interactive prompt and/or ISE shortcut key are not obligated to follow the rules for discoverability, IMO. ### Fixing consistency issues with Powershell’s filename properties Friday, March 27th, 2009 When I decided to bitch about select-object, I used â€œdirâ€ in my examples for a reason: passing filesystem objects around the pipeline is simultaneously one of the most common yet one of the most annoying things you can do in Powershell. Letâ€™s review some built-in cmdlets from the POV of getting the full path to a given file or folder:  Cmdlet Input property name accepted on pipeline Output property name on object sent to pipeline Get-Item, Get-ChildItem, Set-Item Path FullName Join-Path, Split-Path, Test-Path Path n/a Resolve-Path, Select-String Path Path* New-Item Name FullName * note that the built-in documentation for Resolve-Path is wrong! as of CTP3, itâ€™s claiming the output is a string Plus, you may be working with 3rd party tools that obey none of the above conventions. For example, with the TFS Power Tools, Hyung had the foresight to add string[] to the list of types we can convert to QualifiedItems behind the scenes using extra constructors, but we still donâ€™t support the ValueFromPipelineByPropertyName idiom thatâ€™s needed to make non-trivial filesystem objects flow. Furthermore, the objects we output are native TFS types, which were never designed with Powershell in mind. Most of the time thereâ€™s little overlap between these objects and non-TFS cmdlets, but a big exception is the LocalItem property of the ExtendedItem object returned by â€œtfprop.â€ In hindsight, we should have added a NoteProperty that aliased it to Path. (â€¦or FullName, or Name, depending on whether the PS guys make up their mind :) ) Then there are tools that have nothing at all to do with Powershell. Some sort of wrapper script is going to be required regardless, but without consistency in the kind of input your wrapper can expect, it can get needlessly complex. My solution to all of the above is outlined below. Time to let the code do the talking:  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 function get-fullpath { [CmdletBinding()] Param( [Parameter(ParameterSetName="ByPropertyName", Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true, ValueFromPipeline=$true)]                [Alias("FullName")]         [Alias("FullPath")]         # [Alias("Name")] - too many false positives         [Alias("Path")]         [Alias("LocalItem")]         [string] $file ) Process { # eliminate nonexistent files, obviously, but also random objects that were coerced from the pipeline by calling their ToString() method if (!(test-path$file))             { break }                 # expand relative paths (in case the immediate window's workingdir != the workingdir of powershell_ise.exe)         resolve-path $file | # and also make sure UNC paths are in normal format, not Powershell format convert-path } } Who knew that ValueFromPipelineByPropertyName worked on aliases? I didnâ€™t. I thought it would be nice, but since I didnâ€™t see it documented anywhere, my initial sketch for this function used a ton of ugly ParameterSets as a workaround. Somewhere in there I decided to try Aliases; lo and behold, it works! Yay. This function is useful on its own â€“ if nothing else I use it as a wholesale replacement for resolve-path, whose behavior on UNC paths is bizarre in my opinion â€“ but its real value is as a helper. For example, a couple snippets from my$profile:

 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 function tfedit  { [CmdletBinding()]     Param(         [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]         [psobject] $file, [Parameter()] [switch]$PassThru = $false ) Process { if ($PassThru)             { $file } tfpend -edit ($file | get-fullpath)     } }

 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 function ise  {     [CmdletBinding()]     param (         [parameter(Mandatory=$True, Position=0, ValueFromPipeline=$True)]         [psobject]$file, [Parameter()] [switch]$PassThru = $false ) process { if ($PassThru)             { $file }$psise.CurrentOpenedRunspace.OpenedFiles.Add(($file | get-fullpath)) } } I think the PS parser should be smart enough that double-parens are not required in the second example, but oh well. Pretty simple overall, and super convenient. Enjoy! ### Select-Object is annoying Thursday, March 26th, 2009 Oh how I long for a real Select command. Iâ€™m not a SQL-head by any stretch, but I recognize the need for basic set operations like projection. The language geeks in DevDiv surely do too: witness LINQ, and to some degree, Powershell itself. However, I argue PS is falling short on this front. dir | select fullname, lastwritetime FullName LastWriteTime -------- ------------- C:\Users\rberg\Contacts 2/19/2009 12:34:40 PM C:\Users\rberg\Desktop 3/24/2009 12:49:31 PM C:\Users\rberg\Documents 3/23/2009 4:34:23 PM C:\Users\rberg\Downloads 3/25/2009 6:38:20 PM C:\Users\rberg\Favorites 2/19/2009 12:34:43 PM C:\Users\rberg\Links 3/10/2009 3:36:02 PM C:\Users\rberg\Music 2/19/2009 1:26:36 PM C:\Users\rberg\Pictures 2/19/2009 1:26:38 PM C:\Users\rberg\Saved Games 2/19/2009 12:34:40 PM C:\Users\rberg\Searches 2/19/2009 12:34:49 PM C:\Users\rberg\Videos 2/19/2009 1:26:39 PM C:\Users\rberg\.kdiff3rc 3/25/2009 5:23:33 PM C:\Users\rberg\_lesshst 3/4/2009 3:01:33 PM It looks like itâ€™s doing something useful, right? But the more I use Powershell, the more it seems like itâ€™s violating its own guidelines like â€œinput & output should be useful objects, not text-based hacksâ€ and â€œdesign for the middle of the pipeline.â€ Letâ€™s examine what Select-Object really returns. PS >dir .\foo.txt | select name | gm TypeName: Selected.System.IO.FileInfo Name MemberType Definition ---- ---------- ---------- Equals Method System.Boolean Equals(Object obj) GetHashCode Method System.Int32 GetHashCode() GetType Method System.Type GetType() ToString Method System.String ToString() Name NoteProperty System.String Name=foo.txt Thatâ€™s not a Name, thatâ€™s something else entirely. Besides, I get the feeling itâ€™s not being completely honest with meâ€¦doesnâ€™t a FileInfo object have more methods?â€¦ PS >(dir .\foo.txt | select name).gettype() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True False PSCustomObject System.Object Caught ya! Liar! I wouldnâ€™t mind a little custom magic going on under the hood if it meant more power in the userâ€™s hands. But in reality, these PSCustomObjects being tossed around seem to be of very little utility. In almost every case, simply returning the native type (e.g. a System.String in the case of â€œselect nameâ€) would play nicer with the rest of the pipeline. I understand thereâ€™s a tough barrier when you start selecting multiple objects, or when you want lazy evaluation -- but sorry guys, the current solution ainâ€™t it. To be clear, I want to be able to do stuff like:$a = dir foo.txt | select lastwritetime
# oops, we donâ€™t have a real DateTime
$b =$a.adddays(1)

# oops, neither a string nor a â€˜nameâ€™ property
dir *.exe | select basename | get-process

# not even the NoteProperties work in a useful manner
dir | select psparentpath | convert-path

# wouldnâ€™t this be cool?  PS already has quasi tuple syntax, after all
$x,$y = dir | select attributes, length

# or this?  assume do-something is a cmdlet that takes 3 strongly typed parameters by position
# and/or by property name
dir | select fullname â€“as path, versioninfo, isreadonly â€“as force | do-something

Am I crazy to want these to work?  Anyone know of ongoing work in this area before I waste some hobby time? :)

### Brilliant Powershell posts

Thursday, March 26th, 2009

Working at Microsoft, itâ€™s almost impossible to not stay plugged into the technology zeitgeist.  Until you get your email filters set up properly â€“ inevitably running into the Exchange limits â€“ your inbox is literally a news ticker.  Needless to say, establishing that kind of community doesnâ€™t happen automatically in the â€œreal world.â€  So in the last week or two Iâ€™ve been fleshing out my RSS feed subscriptions.

In the process, Iâ€™ve come across some people doing seriously amazing things with Powershell.  Some of these efforts I hope I can find time to contribute and extend; others Iâ€™ll just sit back in awe.  (Iâ€™m not saying which is whichâ€¦you too will have to subscribe!)

Happy scripting!

### TFS Powershell cmdlets default to -noprompt

Monday, March 16th, 2009

The title kinda says it all.  But as usual, thereâ€™s a backstory to tell and some ramifications to warn you about.

First, letâ€™s look back at the legacy tf.exe command line.  In the effort to be both user friendly and flexible, its behavior has actually become quite complicated.  As of TFS 2008, the logic appears to be evaluated in this order of precedence:

1. If the /prompt flag is present for this command, always use Prompt mode, otherwise
2. If the /noprompt flag is present for this command, always use Noprompt mode, otherwise
3. If the tf.exe processâ€™ stdout stream is redirected, use Noprompt mode (unless the environment variable TFS_IGNORESTDOUTREDIRECT is set), otherwise
4. If executing in script mode with a @command file, use Noprompt mode (unless the most recent invocation of the setnoprompt command was â€œsetnoprompt falseâ€), otherwise
5. Default to Prompt mode

Yikes.  Iâ€™d actually forgotten just how many variables there were until I started running tests for this blog post.  Suffice to say, the Powershell cmdlets approach things in a much simpler way:

1. If the â€“prompt switch is present for this cmdlet, always use Prompt mode, otherwise
2. Default to Noprompt mode

Simple enough.  I know a lot of power users will rejoice at this, having long since resigned themselves to /i muscle memory lest random WinForms interrupt their console workflow.  However, there are ramifications to this behavior that you should be aware of.  â€œPrompt modeâ€ means more than simply popping up dialogs.  First of all, there are some UI features that are still not accessible in Prompt mode without additional parameters, such as tf diff /configure.  More importantly, leaving Prompt mode turns off a number of safeguards:

• Undo will permanently discard your local changes.  (I think TFS should use the Recycle Bin, but thatâ€™s another showâ€¦)
• Shelve /delete will permanently delete shelvesets.  (The server used to keep them around until a cleanup job ran, but again thatâ€™s the way things work now.)
• Destroy, well, destroys.

These arenâ€™t the only examples; you get the idea.  None of this behavior is new to TFS in general, but with the default settings of the Powershell tool, itâ€™s a lot easier to shoot yourself in the foot.  Even good old Checkin is likely to surprise you the first time you see it simply go without any further interaction.  [a â€œno blank commentsâ€ checkin policy might help here, nudge nudge]

So have fun, just be careful.  If youâ€™re using the power tools then you are ready to be a power user, right? :)