The TFPT PowerShell snapin is a nice start but it is missing a key cmdlet IMO and that is Get-TfsPermission. Since TFS allows you to add individual users all over your directory structure, it can be quite hard rationalizing exactly who has access to what without traversing a bunch of directories in the Team Foundation client’s Source Control window, opening the Properties dialog and inspecting the Security settings. Fortunately there is also a command line way to do this via TFS’s tf.exe utility. Unfortunately the output of this command is just text and what’s worse is that this text is a pain to query over using PowerShell’s built-in querying cmdlets. So I wrote the following function to convert this text into objects which enables easier querying:
<#
.SYNOPSIS
Gets the Team Foundation server permissions for items based on the specified paths.
.DESCRIPTION
Gets the Team Foundation server permissions for items based on the specified paths and outputs rich
objects as opposed to text.
.PARAMETER Path
The path to the Team Foundation server item.
.PARAMETER LiteralPath
Specifies a path to one or more locations. Unlike Path, the value of LiteralPath is used exactly as it
is typed. No characters are interpreted as wildcards. If the path includes escape characters, enclose
it in single quotation marks. Single quotation marks tell Windows PowerShell not to interpret any
characters as escape sequences.
.PARAMETER Recurse
Gets permissions for the items at the specified location and in all child locations.
.EXAMPLE
C:\PS> Get-TfsPermission C:\Tfs\Acme\Branches\Release\3.7 | Select ServerItem -exp Identities
Get the permissions for the specified folder and expands the Identities array to show each identity.
The server item corresponding to each identity object is tagged onto the object.
.EXAMPLE
C:\PS> Get-TfsPermission C:\Tfs\Acme | Select ServerItem -expand Identities | Where {$_.Allow -match 'Checkin' } | Format-List ServerItem,Identity,Allow,Deny
This command looks for items where the 'checkin' privilege has been granted locally.
.NOTES
Author: Keith Hill
Date: June 28, 2010
#>
function Get-TfsPermission
{
[CmdletBinding(DefaultParameterSetName="Path")]
param(
[Parameter(Mandatory=$true, Position=0, ParameterSetName="Path",
ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true,
HelpMessage="Path to workspace item")]
[ValidateNotNullOrEmpty()]
[string[]]
$Path,
[Alias("PSPath")]
[Parameter(Mandatory=$true, Position=0, ParameterSetName="LiteralPath",
ValueFromPipelineByPropertyName=$true,
HelpMessage="Path to workspace item")]
[ValidateNotNullOrEmpty()]
[string[]]
$LiteralPath,
[Parameter()]
[switch]
$Recurse
)
Begin
{
Set-StrictMode -Version Latest
$item = $null
}
Process
{
if ($psCmdlet.ParameterSetName -eq "Path")
{
# In the -Path (non-literal) case we may need to resolve a wildcarded path
$resolvedPaths = @($Path | Resolve-Path | Convert-Path)
}
else
{
# Must be -LiteralPath
$resolvedPaths = @($LiteralPath | Convert-Path)
}
foreach ($rpath in $resolvedPaths)
{
Write-Verbose "Processing $rpath"
$tfargs = ''
$tfargs += if ($Recurse) { '/r' }
# Look at each line of TF PERMISSION output and use a regex to determine
# what the data for the line is.
switch -regex (tf permission $rpath $tfargs)
{
'^Server item:\s+(\S+)\s+\(Inherit:\s+(\w+)\)'
{
# If previous object exists, output it before creating a new one
if ($item) { $item }
$item = new-object psobject -property @{
ServerItem = $matches[1]
Inherits = $matches[2] -eq 'yes'
Identities = @()
}
$item.psobject.TypeNames[0] = "TfsTools.VersionControl.ItemPermissions"
}
'\bIdentity:\s+(.*)$'
{
$identityName = $matches[1]
$currentIdentity = new-object psobject -property @{
Identity = $identityName
Allow = ''
Deny = ''
InheritedAllow = ''
InheritedDeny = ''
}
$item.Identities += $currentIdentity
}
'\bAllow:\s*(.*)$'
{
$currentIdentity.Allow = $matches[1]
}
'\bDeny:\s*(.*)$'
{
$currentIdentity.Deny = $matches[1]
}
'\bAllow\s+\(Inherited\)\s*:\s*(.*)$'
{
$currentIdentity.InheritedAllow = $matches[1]
}
'\bDeny\s+\(Inherited\)\s*:\s*(.*)$'
{
$currentIdentity.InheritedDeny = $matches[1]
}
}
}
}
End
{
# Output the very last object
if ($item) { $item }
}
}
The output of this function isn’t great for viewing since it is primarily intended to enable querying of the permission info on each server item. Note to the Team Foundation Power Tools devs, it sure would be nice to get this functionality into a future TFPT drop.
Occasionally I need to tail some very large log files over the network. In this scenario, the standard PowerShell approach:
PS> Get-ChildItem *.log | %{ Get-Content $_ | Select –Last 1 }
just isn’t practical for performance reasons. So the final drop of PSCX 1.2 will have a new cmdlet called Tail-File that is optimized for tailing the end of large (even huge) log files. Here’s a performance comparison of the two approachs:
19# measure-command { tail-file \\Keith-PC\C\*.txt -count 1 }
Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 7
Ticks : 70813
TotalDays : 8.19594907407407E-08
TotalHours : 1.96702777777778E-06
TotalMinutes : 0.000118021666666667
TotalSeconds : 0.0070813
TotalMilliseconds : 7.0813
20# measure-command {Get-ChildItem \\Keith-PC\C\*.txt | %{ Get-Content $_ | Select -Last 1 }}
Days : 0
Hours : 0
Minutes : 0
Seconds : 17
Milliseconds : 656
Ticks : 176567027
TotalDays : 0.000204359984953704
TotalHours : 0.00490463963888889
TotalMinutes : 0.294278378333333
TotalSeconds : 17.6567027
TotalMilliseconds : 17656.7027
That is a speed-up of approximately 2500x. That is pretty significant especially if you need to do this to a number of large log files. For reference, the above test tailed two medium size log files (total size: 37.4 MB):
-a--- 5/23/2009 6:33 AM 4173558 dirlist.txt
-a--- 5/23/2009 6:33 AM 35053946 filelist.txt
Tail-File handles ASCII and Unicode encoding as well UTF8 as long as it contains only ASCII characters i.e. it will not choke on the UTF8 byte order mark. Oh yeah, it also supports active tailing (tail –f) via the Wait parameter. Use Ctrl+C to return control back to the console.
New Scripts
There are several facets to the subject of errors in PowerShell that you should understand to get the most out of PowerShell. Some of these facets are error handling, error related global variables and error related preference variables. But the most fundamental facet is the distinction between “terminating” and “non-terminating” errors.
Terminating Errors
Terminating errors will be immediately familiar to software developers who deal with exceptions. If an exception is not handled it will cause the program to crash. Similarly if a terminating error is not handled it will cause the current operation (cmdlet or script) to abort with an error. Terminating errors and are generated by:
The gist of a terminating error is that the code throwing the terminating error is indicating that it cannot reasonably continue and is aborting the requested operation. As we will see later, you as the client of that code, have the ability to declare that you can handle the error and continue executing subsequent commands. Terminating errors that are not handled propagate up through the calling code, prematurely terminating each calling function or script until either the error is handled or the original invoking operation is terminated.
Here is an example of how a terminating error alters control flow:
PS> "Before"; throw "Oops!"; "After"
Before
Oops!
At line:1 char:16
+ "Before"; throw <<<< "Oops!"; "After"
+ CategoryInfo : OperationStopped: (Oops!:String) [], RuntimeException
+ FullyQualifiedErrorId : Oops!
Note that “After” is not output to the console because “throw” issues a terminating error.
Non-terminating Errors
Have you ever experienced the following in older versions of Windows Explorer? You open a directory with a large number of files, say your temp dir, and you want to empty it. You select the entire contents of the directory, press Delete and wait. Unfortunately some processes invariably have files open in the temp directory. So after deleting a few files, you get an error from Windows Explorer indicating that it can’t delete some file. You press OK and at this point Windows Explorer aborts the operation. It treats the error effectively as a terminating error. This can be very frustrating. You select everything again, press Delete, Explorer deletes a few more files then errors and aborts again. You rinse and repeat these steps until finally all the files that can be deleted are deleted. This behavior is very annoying and wastes your time. In an automation scenario, premature aborts like this are often unacceptable.
Having a special category of error that does not terminate the current operation is very useful in scenarios like the one outlined above. In PowerShell, that category is the non-terminating error. Even though a non-terminating error does not terminate the current operation, the error is still logged to the $Error collection (discussed later) as well as displayed on the host’s console as is the case with terminating errors. Non-terminating errors are generated by:
Here is an example of how a non-terminating error does not alter control flow:
PS> "Before"; Write-Error "Oops!"; "After"
Before
"Before"; Write-Error "Oops!"; "After" : Oops!
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException
After
Note the Write-Error command issues a non-terminating error that gets displayed on the host’s console then the script continues execution.
Error Variables
There are several global variables and global preference variables related to errors. Here is a brief primer on them:
The $Error global variable can be used to inspect the details of up to the last $MaximumErrorCount number of errors that have occurred during the session e.g.:
PS> $error[0] | fl * -force
PSMessageDetails :
Exception : System.IO.IOException: The process cannot access the file '\Temp\FX
SAPIDebugLogFile.txt' because it is being used by another process.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.FileInfo.Delete()
at Microsoft.PowerShell.Commands.FileSystemProvider.RemoveFileSystemItem(FileSystemInfo file
SystemInfo, Boolean force)
TargetObject : \Temp\FXSAPIDebugLogFile.txt
CategoryInfo : WriteError: (\Temp\FXSAPIDebugLogFile.txt:FileInfo) [Remove-Item], IOException
FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
ErrorDetails : Cannot remove item \Temp\FXSAPIDebugLogFile.txt: The process cannot
access the file '\Temp\FXSAPIDebugLogFile.txt' because it is being
used by another process.
InvocationInfo : System.Management.Automation.InvocationInfo
PipelineIterationInfo : {0, 1}
As the output above shows, errors in PowerShell are not just strings but rich objects. The object may be a .NET exception with an embedded error record or just an error record, The error record contains lots of useful information about the error and the context in which it occurred.
The default output formatting of errors can be a bit hard to digest. The PowerShell Community Extensions come with a handy Resolve-Error function that digs through the error information and surfaces the important stuff e.g.:
PS> Resolve-Error # displays $error[0] by default
…
PS> Resolve-Error $error[1]
…
The $? global variable is handy for determining if the last operation encountered any errors e.g.:
PS> Remove-Item $env:temp\*.txt -Recurse -Verbose
VERBOSE: Performing operation "Remove File" on Target "...\Temp\foo.txt".
VERBOSE: Performing operation "Remove File" on Target "...\Temp\FXSAPIDebugLogFile.txt".
WriteError: (...\Temp\DebugLogFile.txt:FileInfo) [Remove-Item], IOException
PS> $?
False
In this case, the Remove-Item cmdlet only partially succeeded. It deleted two files but then encountered a non-terminating error. This failure to achieve complete success i.e. no errors, is indicated by $? returning False.
Working with Non-Terminating Errors
Sometimes you want to completely ignore non-terminating errors. Who wants all that red text spilled all over their console especially when you don’t care about the errors you know you're going to get. You can suppress the display of non-terminating errors either locally or globally. To do this locally, just set the cmdlet’s ErrorAction parameter to SilentlyContinue e.g.
Remove-Item $env:temp\*.txt -Recurse -Verbose -ErrorAction SilentlyContinue
For interactive scenarios it is handy to use 0 instead of SilentlyContinue. This works because SilentlyContinue is part of a enum and its integer value is 0. So to save your wrists you can rewrite the above as:
ri $env:temp\*.txt -r -v –ea 0
Note that for a script I would use the first approach for readability.
To accomplish the above globally, set the $ErrorActionPreference global preference variable to SilentlyContinue (or 0). This will cause all non-terminating errors in the session to not be displayed on the host’s console. However they will still be logged to the $Error collection.
Setting the $ErrorActionPreference to Stop can be useful in the following scenario. If you misspell a command, PowerShell will generate a non-terminating error as shown below:
PS> Copy-Itme .\_lesshst .\_lesshst.bak; $?; "After"
The term 'Copy-Itme' is not recognized as the name of a cmdlet, function, scrip
t file, or operable program. Check the spelling of the name, or if a path was i
ncluded, verify that the path is correct and try again.
At line:1 char:10
+ Copy-Itme <<<< .\_lesshst .\_lesshst.bak; $?; "After"
+ CategoryInfo : ObjectNotFound: (Copy-Itme:String) [], CommandNo
tFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
False
After
In this case, the misspelled Copy-Itme command failed ($? returned False) but since the error was non-terminating, the script continues execution as shown by the output “After”.
If you are hard-core about correctness you can get PowerShell to convert non-terminating errors into terminating errors by setting $ErrorActionPreference to Stop which has global impact. You can also do this one a cmdlet by cmdlet basis by setting the cmdlet’s –ErrorAction parameter to Stop.
The last issue to be aware of regarding non-terminating errors is that a Windows executable that returns a non-zero exit code does not generate any sort of error. The only action PowerShell takes is to set $? to False if the exit code is non-zero. There is no error record created and stuffed into $Error. In many cases, the failure of an external executable means your script cannot continue. In this case, it is desirable to convert a failure exit code into a terminating error. This can be done easily using the function below:
function CheckLastExitCode {
param ([int[]]$SuccessCodes = @(0), [scriptblock]$CleanupScript=$null)
if ($SuccessCodes -notcontains $LastExitCode) {
if ($CleanupScript) {
"Executing cleanup script: $CleanupScript"
&$CleanupScript
}
$msg = @"
EXE RETURNED EXIT CODE $LastExitCode
CALLSTACK:$(Get-PSCallStack | Out-String)
"@
throw $msg
}
}
Note that Get-PSCallStack is specific to PowerShell v2.0. Invoke CheckLastExitCode right after invoking an executable, well at least for those cases where you care if an executable returns an error. This function provides a couple of handy features. First, you can specify an array of acceptable success codes which is useful for exes that return 0 for failure and 1 for success and is also useful for exes that return multiple success codes. Second, you specify a cleanup scriptblock that will get executed on failure.
Handling Terminating Errors
Handling terminating errors in PowerShell comes in two flavors. Using the trap keyword which is supported in both version 1 and 2 of PowerShell. Using try { } catch { } finally { } which is new to version 2.
Trap Statement
Trap is a mechanism available in other shell languages like Korn shell. It effectively declares that either any error type or a specific error type is handled by the scriptblock following the trap keyword. Trap has the interesting property that where ever it is declared in a scope, it is valid for that entire scope e.g.:
Given the following script (trap.ps1):
"Before"
throw "Oops!"
"After"
trap { "Error trapped: $_" }
Invoking it results in the following output:
PS> .\trap.ps1
Before
Error trapped: Oops!
Oops!
At C:SERS\KEITH\TRAP.PS1:2 CHAR:6
+ THROW <<<< "OOPS!"
+ CATEGORYINFO : OPERATIONSTOPPED: (OOPS!:STRING) [], RUNTIMEEXCEPTION
+ FULLYQUALIFIEDERRORID : OOPS!
AFTER
NOTE THAT IT DOESN’T MATTER THAT THE TRAP STATEMENT IS AFTER THE LINE THAT THROWS THE ERROR. ALSO NOTE THAT SINCE THE DEFAULT VALUE FOR $ERRORACTIONPREFERENCE IS 'CONTINUE', THE ERROR IS DISPLAYED, LOGGED TO $ERROR BUT EXECUTION RESUMES AT THE NEXT STATEMENT. NOTE: WITHIN THE CONTEXT OF A TRAP STATEMENT, $_ REPRESENTS THE ERROR THAT WAS CAUGHT.
Another thing to consider is whether to use Write-Host or Write-Output to display text in the trap statement. The example above implicitly uses Write-Output. This has the benefit that the text can be redirected to a log file. The downside is that if the exception is handled and execution continues, that text will become part of the output for that scope which, in the case of functions and scripts, may not be desirable.
If you want to execute cleanup code on failure but still terminate execution, we can change the trap statement to use the break keyword. Consider the following script:
function Cleanup() {"cleaning up"}
trap { "Error trapped: $_"; continue }
"Outer Before"
& {
trap { Cleanup; break }
"Inner Before"
throw "Oops!"
"Inner After"
Cleanup
}
"Outer After"
Note that the inner trap calls the Cleanup function but then propagates the error. As a result, the “Inner After” statement never executes because control flow is transferred outside the scope of the trap statement. The outer trap then catches the error, displays it and continues execution. As a result, the “Outer After” statement is executed.
The interaction between the control flow altering keywords valid in a trap statement (break, continue and return), the $ErrorActionPreference variable if no control flow altering keyword is used and the final behavior is somewhat complex as is demonstrated by the table below:
Trap Behavior:
Keyword Used Rely on $ErrorActionPreference Displays Error Propagates Error break Stop True True continue SilentlyContinue False False return Continue True False return<object>* N/A True False N/A Inquire Depends upon response Depends upon response* <object> is appended to the end of the trap scope’s output.
All of the examples of trap shown above trap all errors. You may want to trap only specific errors. You can do this by specifying the type name of an exception to trap as shown below:
trap [System.DivideByZeroException] { "Please don't divide by 0!"}
$divisor = 0
1/$divisor
If you want to execute different code for different errors, you can define multiple trap statements in your script:
trap [System.DivideByZeroException] { "Please don't divide by 0!"}trap [System.Management.Automation.CommandNotFoundException] { "Did you fat finger the command name?" }
trap { "Anything not caught by the first two traps gets here" }
If you define multiple trap statements for the same error type the first one wins and the others within the same scope are ignored.
Try / Catch / Finally
Version 2 of Windows PowerShell introduces try/catch/finally statements - a new error handling mechanism that most developers will be immediately familiar with. There are two main differences between trap and try/catch/finally. First, a trap anywhere in a lexical scope covers the entire lexical scope. With a try statement, only the script within the try statement is checked for errors. The second difference is that trap doesn’t support finally behavior i.e., always execute the finally statement whether the code in the try statement throws a terminating error or not. In fact, any associated catch statements could also throw a terminating error and the finally statement would still execute.
You can fake finally behavior with trap by calling the same “finally” code from the end of the lexical scope *and* from the trap statement. Consider the Cleanup function from the earlier example. We want to always execute Cleanup whether the script errors or not. The example shown in the previous section using the Cleanup function works OK unless the Cleanup function throws a terminating error. Then you run into the issue where Cleanup gets called again due to the trap statement. This sort of cleanup is much easier to represent in your script using try/finally e.g.:
function Cleanup($err) {"cleaning up"}
trap { "Error trapped: $_"; continue }
"Outer Before"
try {
"Inner Before"
throw "Oops!"
"Inner After"
}
finally {
Cleanup
}
"Outer After"
This example results in Cleanup always getting called whether or not the script in the try statement generates a terminating error. It also shows that you can mix and match trap statements with try/catch/finally.
One last example shows how you can use catch to handle different error types uniquely:
function Cleanup($err) {"cleaning up"}
trap { "Error trapped: $_"; continue }
"Outer Before"
try {
"Inner Before"
throw "Oops!"
"Inner After"
}
catch [System.DivideByZeroException] {
"Please don't divide by 0!"
}
catch [System.Management.Automation.CommandNotFoundException] {
"Did you fat finger the command name?"
}
catch {
"Anything not caught by the first two catch statements gets here"
}
finally {
Cleanup
}
"Outer After"
The use of the finally statement is optional as is the catch statement. The valid combinations are try/catch, try/finally and try/catch/finally.
In summary, PowerShell’s error handling capabilities are quite powerful especially the ability to distinguish between non-terminating and terminating errors. With the addition of the new try/catch/finally support in version 2.0 the important scenario of resource cleanup is easy to handle.
Both Windows PowerShell and PSCX made it into Scott Hanselman’s 2009 Ultimate Developer and Power Users Tool List for Windows. w00t!
Tonight I gave a presentation on the new features in Windows PowerShell 2.0. I also demo how to create a Windows Troubleshooting Platform that is new in Windows 7. WTP uses PowerShell scripts to do the heavy lifting of detecting and resolving root causes i.e. problems that need fixing. I gave this presentation at the Northern Colorado.NET Special Interest Group. As promised, here are the presentation materials: slide deck and Start-Demo samples.
I’m relaying this invite from Hal & Jonathan – hosts of the PowerScripting Podcast.
Windows isn’t just about the GUI. Starting with Windows 7, you have built-in access to PowerShell version 2, an object-oriented scripting language and command shell. Please join PowerScripting Podcast hosts Jonathan Walz and PowerShell MVP Hal Rottenberg as they interview Distinguished Engineer Jeffrey Snover on launch day! Jeffrey is the chief architect responsible for PowerShell at Microsoft, and he’ll be covering what’s new with the tool and why every system administrator on the planet needs to be using it. If you’ve never attended PowerScripting Live, you are missing out on a great time. The show will be streamed live via Ustream, and viewers can chat with each other, as well as submit questions for the guest.
Congratulations to the Windows and Windows PowerShell teams for two very excellent releases. I’ve been using Windows 7 daily since January’s beta release and PowerShell 2.0 drops for even longer. Both products are destined to be smash hits in my humble opinion. I’m very excited about the new capabilities of PowerShell 2.0 and the fact that it’s a built-in and required component of Windows 7 means that the time when we can count on PowerShell “just being there” is getting closer.
One of the lesser known features in PowerShell 2.0 is that it supports string localization and pretty simply I might add. Now for most developers and admins script localization probably isn’t going to be something you’ll worry about. However if you are providing PowerShell based solutions to an international audience this feature will come in very handy in terms of broadening your reach.
I believe the driving force behind this feature’s inclusion in PowerShell is that a critical component of Windows 7 – the Windows Troubleshooting Platform and associated troubleshooting packs – use PowerShell scripts extensively. Obviously Windows is localized to the extreme so it stands to reason that the troubleshooting scripts, which can interact with the end user via text prompts, were required to be localized. Enough context. Let’s look at how localization works in PowerShell 2.0.
First, the feature in PowerShell is referred to as “Data Sections” and is covered by the help topics:
Let’s start by creating our string table file. This is created as a .psd1 (data) file. In this case, we will call it messages.psd1 and its contents are simply:
# Contents of .\messages.psd1This is a bit odd looking for a “data” file but the gist is that the ConvertFrom-StringData cmdlet returns a ordinary hashtable. The string that ConvertFrom-StringData operates on has to be in a special format which is essentially one key/value pair per line where the key and value are separated by an “=”. The “key” is how you will reference the “value” (actual localized string) from your script. FWIW you could just have the psd1 file return a hashtable directly but then you have to quote each value. So the format above is a bit cleaner and more like a traditional string table file.
Now at this point you could and might be tempted to do something like this in your script:
$msgs = .\messages.psd1But there is one tiny little problem - .psd1 files are not executable like .ps1 or .psm1 files are. Hey, that’s probably why they are called “data” files. :-) Obviously PowerShell must provide a way to load these files and that mechanism is the Import-LocalizedData cmdlet. It takes the path to a .psd1 file, loads it and then assigns the resulting hashtable to a variable name provided to the cmdlet. Let’s see an example of this:
# Contents of test.ps1The output of this script is:
PS> .\test.ps1
Hello John
Goodbye
You may be thinking that this seems like a pretty simple task, not worthy of requiring a dedicated cmdlet. However there is one very important piece of “non-trivial” functionality that the Import-LocalizedData cmdlet provides and that is it searches through various culture specific sub directories looking for a matching .psd1 file for the user’s culture. For those familiar with .NET development this is very similar to how the .NET binder looks through application’s base sub dirs for the appropriate satellite assembly for the user’s culture. Let’s see an example of how this works.
First, let’s create a German version of our string resources and put the file in a sub dir titled de-DE. The name of this sub dir is important. The name reflects the <language>-<region> that the localized strings target. Here are the contents of .\de-DE\messages.psd1:
# Contents of .\de-DE\messages.psd1At this point our dir structure looks like this:
.\test.ps1
.\messages.psd1 (English – fallback)
.\de-DE\messages.psd1
Now I need to introduce a somewhat orthogonal but very handy function called Using-Culture that the PowerShell team blogged a long time ago. I have updated it for PowerShell 2.0 and I use it to effectively simulate running PowerShell scripts in different cultures.
function Using-Culture ([System.Globalization.CultureInfo]$culture =(throw "USAGE: Using-Culture -Culture culture -Script {scriptblock}"),Now let’s try running out test.ps1 script under the German language/region:
PS> Using-Culture de-DE { .\test.ps1 }
Wie Geht John
Auf Wiedersehen
Now that’s what I’m talking about! Script localization without requiring an advanced degree. Now, you might ask – what happens when somebody run’s in a culture that I have not localized for? Let’s see:
PS> Using-Culture fr-FR { .\test.ps1 }
Hello John
Goodbye
When I listed the dir contents above I mentioned that the top-level messages.psd1 was the “fallback”. Essentially if PowerShell can’t find the specified data file for the current culture it will “fallback” to the top-level data file or .\messages.psd1 in this case (which is English). This may very well be a feature you never use but if you desire to reach a broad audience with your PowerShell 2.0 modules, consider pulling out your strings into a data file even if you only provide a data file for your native language. This gives others a much better chance of localizing your module into other languages for you!
Just ran across a new feature of PowerShell 2.0 that is convenient. Need the path to the various profile scripts on a system? Try this:
PS> $profile.psextended | Format-List
AllUsersAllHosts : C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
AllUsersCurrentHost : C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1
CurrentUserAllHosts : C:SERS\KEITH\DOCUMENTS\WINDOWSPOWERSHELL\PROFILE.PS1
CURRENTUSERCURRENTHOST : C:SERS\KEITH\DOCUMENTS\WINDOWSPOWERSHELL\MICROSOFT.POWERSHELL_PROFILE.PS1
TO ACCESS INDIVIDUALLY YOU CAN USE $PROFILE.ALLUSERSALLHOSTS.
PSMDTAG:VARIABLE PROFILE
I had a need to find all PInvokes in an assembly. At first I thought about searching the source code since I had it but ran into various problems:
[Updated: 11-02-2009] I decided to go with the "reflection only load" approach to extract the PInvoke (DllImport) information. The script file has been updated to reflect (pun intended) this new and better approach. In fact, more data is gathered for each PInvoke including all of the values for the associated DllImportAttribute. Now to see if I can get this included in the next version of PSCX. It seems that the reflection only load is sensitive to assembly load failures. Once an assembly fails to load you pretty much have to start a new PowerShell session to try again. I'll keep looking into this. For what appears to be a really neat capability (ReflectionOnlyLoad) it has some unfortunate short-comings.
Here is some sample output when run on the PSCX snapin dlls.
PS> gci C:SERS\KEITH\PSCX\TRUNK\SRC\PSCX\BIN\DEBUG\PSCX*.DLL | .\FIND-PINVOKE |
SORT DLLNAME,ASSEMBLYNAME,METHODNAME | FT ASSEMBLYNAME,METHODNAME,TYPENAME -GROUPBY DLLNAME -AUTO
DllImport: advapi32.dll
Assembly MethodName TypeName
-------- ---------- --------
Pscx.Core.dll AdjustTokenPrivileges Pscx.Interop.NativeMethods
Pscx.Core.dll GetTokenInformation Pscx.Interop.UnsafeNativeMethods
Pscx.Core.dll LogonUser Pscx.Interop.NativeMethods
Pscx.Core.dll LookupPrivilegeName Pscx.Interop.NativeMethods
Pscx.Core.dll LookupPrivilegeValue Pscx.Interop.NativeMethods
Pscx.Core.dll OpenProcessToken Pscx.Interop.NativeMethods
Pscx.Core.dll OpenThreadToken Pscx.Interop.NativeMethods
DllImport: Fusion.dll
Assembly MethodName TypeName
-------- ---------- --------
Pscx.Core.dll CreateAssemblyCache Pscx.Interop.NativeMethods
Pscx.Core.dll CreateAssemblyEnum Pscx.Interop.NativeMethods
Pscx.Core.dll CreateAssemblyNameObject Pscx.Interop.NativeMethods
Pscx.Core.dll CreateInstallReferenceEnum Pscx.Interop.NativeMethods
Pscx.Core.dll GetCachePath Pscx.Interop.NativeMethods
DllImport: kernel32.dll
Assembly MethodName TypeName
-------- ---------- --------
Pscx.Core.dll CloseHandle Pscx.Interop.NativeMethods
Pscx.Core.dll CreateConsoleScreenBuffer Pscx.Interop.NativeMethods
Pscx.Core.dll CreateFile Pscx.Interop.NativeMethods
Pscx.Core.dll CreateHardLink Pscx.Interop.NativeMethods
Pscx.Core.dll CreateSymbolicLink Pscx.Interop.NativeMethods
Pscx.Core.dll DeleteFile Pscx.Interop.NativeMethods
Pscx.Core.dll DeleteVolumeMountPoint Pscx.Interop.NativeMethods
Pscx.Core.dll DeviceIoControl Pscx.Interop.UnsafeNativeMethods
Pscx.Core.dll FindFirstVolumeMountPoint Pscx.Interop.NativeMethods
Pscx.Core.dll FindNextVolumeMountPoint Pscx.Interop.NativeMethods
Pscx.Core.dll FindVolumeMountPointClose Pscx.Interop.NativeMethods
Pscx.Core.dll FreeLibrary Pscx.Interop.NativeMethods
Pscx.Core.dll GetConsoleFontSize Pscx.Interop.NativeMethods
Pscx.Core.dll GetConsoleInputMode Pscx.Interop.NativeMethods
Pscx.Core.dll GetConsoleOutputMode Pscx.Interop.NativeMethods
Pscx.Core.dll GetConsoleWindow Pscx.Interop.NativeMethods
Pscx.Core.dll GetCurrentConsoleFont Pscx.Interop.NativeMethods
Pscx.Core.dll GetCurrentProcess Pscx.Interop.NativeMethods
Pscx.Core.dll GetModuleHandle Pscx.Interop.NativeMethods
Pscx.Core.dll GetProcAddress Pscx.Interop.NativeMethods
Pscx.Core.dll GetProcAddress Pscx.Interop.NativeMethods
Pscx.Core.dll GetShortPathName Pscx.Interop.NativeMethods
Pscx.Core.dll GetStdHandle Pscx.Interop.NativeMethods
Pscx.Core.dll GetVolumeNameForVolumeMountPoint Pscx.Interop.NativeMethods
Pscx.Core.dll IsWow64Process Pscx.Interop.NativeMethods
Pscx.Core.dll LoadLibrary Pscx.Interop.NativeMethods
Pscx.Core.dll ReadConsoleInput Pscx.Interop.NativeMethods
Pscx.Core.dll ReadFile Pscx.Interop.NativeMethods
Pscx.Core.dll ReadFile Pscx.Interop.NativeMethods
Pscx.Core.dll RemoveDirectory Pscx.Interop.NativeMethods
Pscx.Core.dll SetConsoleActiveScreenBuffer Pscx.Interop.NativeMethods
Pscx.dll __SetVolumeLabel Pscx.Commands.IO.SetVolumeLabel
Since PowerShell is based on .NET and makes it easy to access .NET functionality, PowerShell can use JSON serializer that comes with .NET. The follow snippet of code shows how to deserialize a JSON string into an XML object and then how to take an XML object and serialize it to JSON;
1: Add-Type -Assembly System.ServiceModel.Web,System.Runtime.Serialization 2: 3: function Convert-JsonToXml([string]$json) 4: { 5: $bytes = [byte[]][char[]]$json 6: $quotas = [System.Xml.XmlDictionaryReaderQuotas]::Max 7: $jsonReader = [System.Runtime.Serialization.Json.JsonReaderWriterFactory]::CreateJsonReader($bytes,$quotas) 8: try 9: { 10: $xml = new-object System.Xml.XmlDocument 11: 12: $xml.Load($jsonReader) 13: $xml 14: } 15: finally 16: { 17: $jsonReader.Close() 18: } 19: } 20: 21: function Convert-XmlToJson([xml]$xml) 22: { 23: $memStream = new-object System.IO.MemoryStream 24: $jsonWriter = [System.Runtime.Serialization.Json.JsonReaderWriterFactory]::CreateJsonWriter($memStream) 25: try 26: { 27: $xml.Save($jsonWriter) 28: $bytes = $memStream.ToArray() 29: [System.Text.Encoding]::UTF8.GetString($bytes,0,$bytes.Length) 30: } 31: finally 32: { 33: $jsonWriter.Close() 34: } 35: } 36: 37: $str = '{"fname":"John", "lname":"Doe", "age":42, "addr":{"city":"Gotham", "street1":"123 Guano Way", "state":"NY"}}' 38: 39: $xml = Convert-JsonToXml $str 40: $xml.root.fname 41: $xml.root.lname 42: 43: $json = Convert-XmlToJson $xml 44: $json
Note that this does require .NET 3.5. However it serves to show that when you’re looking for some functionality, if you can’t find it in cmdlet or module form first, check the .NET framework next. You would be surprised what kind of cool functionality you can find in there. There’s a bit more to this story. You can use the WCF DataContractJsonSerializer to bypass XML and directly serialize/deserialize .NET types. I can’t take credit for this idea though – that was Jaykul’s.
Just got the notice today. Woohoo! I expect 2010 to be a banner year for PowerShell as Windows 7 spreads PowerShell out to more folks than ever before and as usual, I’ll do what I can to help folks grok PowerShell.
Fresh from the PowerShell team:
http://technet.microsoft.com/zh-cn/library/bb978526.aspx
http://technet.microsoft.com/zh-tw/library/bb978526.aspx
http://technet.microsoft.com/de-de/library/bb978526.aspx
http://technet.microsoft.com/it-it/library/bb978526.aspx
http://technet.microsoft.com/ko-kr/library/bb978526.aspx
http://technet.microsoft.com/pt-br/library/bb978526.aspx
http://technet.microsoft.com/ru-ru/library/bb978526.aspx
Enjoy!
This came up on StackOverflow and thought it was worth reposting here. It shows how fancy you can get with low-level Win32 API calls using the new Add-Type cmdlet. The following code essentially checks for the existence of *any* window associated with the current process other than the main window. Any additional window like that created for Out-GridView will cause the script to wait until the last of these extra windows are closed. Note that Out-GridView windows are created as top level windows and their titles vary depending on the command text used to invoke the Out-GridView cmdlet.
1: $src = @' 2: using System; 3: using System.Diagnostics; 4: using System.Runtime.InteropServices; 5: using System.Threading; 6: 7: namespace Utils 8: { 9: public delegate bool Win32Callback(IntPtr hwnd, IntPtr lParam); 10: 11: public class WindowHelper 12: { 13: private const int PROCESS_QUERY_LIMITED_INFORMATION = 0x1000; 14: private IntPtr _mainHwnd; 15: private IntPtr _ogvHwnd; 16: private IntPtr _poshProcessHandle; 17: private int _poshPid; 18: private bool _ogvWindowFound; 19: 20: public WindowHelper() 21: { 22: Process process = Process.GetCurrentProcess(); 23: _mainHwnd = process.MainWindowHandle; 24: _poshProcessHandle = process.Handle; 25: _poshPid = process.Id; 26: } 27: 28: public void WaitForOutGridViewWindowToClose() 29: { 30: do 31: { 32: _ogvWindowFound = false; 33: EnumChildWindows(IntPtr.Zero, EnumChildWindowsHandler, 34: IntPtr.Zero); 35: Thread.Sleep(500); 36: } while (_ogvWindowFound); 37: } 38: 39: [DllImport("User32.dll")] 40: [return: MarshalAs(UnmanagedType.Bool)] 41: public static extern bool EnumChildWindows( 42: IntPtr parentHandle, Win32Callback callback, IntPtr lParam); 43: 44: [DllImport("Oleacc.dll")] 45: public static extern IntPtr GetProcessHandleFromHwnd(IntPtr hwnd); 46: 47: [DllImport("Kernel32.dll")] 48: public static extern int GetProcessId(IntPtr handle); 49: 50: [DllImport("Kernel32.dll")] 51: [return: MarshalAs(UnmanagedType.Bool)] 52: public static extern bool DuplicateHandle( 53: IntPtr hSourceProcessHandle, 54: IntPtr hSourceHandle, 55: IntPtr hTargetProcessHandle, 56: out IntPtr lpTargetHandle, 57: int dwDesiredAccess, 58: bool bInheritHandle, 59: int dwOptions); 60: 61: [DllImport("Kernel32.dll")] 62: [return: MarshalAs(UnmanagedType.Bool)] 63: public static extern bool CloseHandle(IntPtr handle); 64: 65: [DllImport("Kernel32.dll")] 66: public static extern int GetLastError(); 67: 68: private bool EnumChildWindowsHandler(IntPtr hwnd, IntPtr lParam) 69: { 70: if (_ogvHwnd == IntPtr.Zero) 71: { 72: IntPtr hProcess = GetProcessHandleFromHwnd(hwnd); 73: IntPtr hProcessDup; 74: if (!DuplicateHandle(hProcess, hProcess, _poshProcessHandle, 75: out hProcessDup, 76: PROCESS_QUERY_LIMITED_INFORMATION, 77: false, 0)) 78: { 79: Console.WriteLine("Dup process handle {0:X8} error: {1}", 80: hProcess.ToInt32(), GetLastError()); 81: return true; 82: } 83: int processId = GetProcessId(hProcessDup); 84: if (processId == 0) 85: { 86: Console.WriteLine("GetProcessId error:{0}", 87: GetLastError()); 88: return true; 89: } 90: if (processId == _poshPid) 91: { 92: if (hwnd != _mainHwnd) 93: { 94: _ogvHwnd = hwnd; 95: _ogvWindowFound = true; 96: CloseHandle(hProcessDup); 97: return false; 98: } 99: } 100: CloseHandle(hProcessDup); 101: } 102: else if (hwnd == _ogvHwnd) 103: { 104: _ogvWindowFound = true; 105: return false; 106: } 107: return true; 108: } 109: } 110: } 111: '@ 112: 113: Add-Type -TypeDefinition $src 114: 115: Get-Process | Out-GridView 116: 117: $helper = new-object Utils.WindowHelper 118: $helper.WaitForOutGridViewWindowToClose() 119: 120: "Done!!!!"
Note the use of DuplicateHandle(). This was required because initially we don’t have permissions to ask for the process ID of the process handle returned from GetProcessHandleFromHwnd. During the handle duplication we can ask for the permission we need to get the process id.
We have released a new beta of PSCX that is complete module based and as such, only works with Windows PowerShell 2.0. You can have both 1.2 and 2.0 installed on the same system. You just alternate between Add-PSSnapin for 1.2 and Import-Module Pscx for 2.0. There is a new Test-Script cmdlet that checks for syntax errors in PowerShell scripts. There is also a few function Invoke-BatchFile that is very handy for importing environment variables created in a batch file. This is most handy for setting up your PowerShell environment for Visual Studio development e.g.:
1: Import-Module Pscx -DisableNameChecking 2: $vcargs = ?: {$Pscx:Is64BitProcess} {'amd64'} {''} 3: $VS90VCVarsBatchFile = "${env:VS90COMNTOOLS}..\..\VC\vcvarsall.bat" 4: Invoke-BatchFile $VS90VCVarsBatchFile $vcargs
Give PSCX 2.0 a try and let us know what you think.
Finally! We’ve gotten PSCX 2.0 finished and released. A few notes about this release. It targets only Windows PowerShell 2.0 as it is module based. Also, the deployment of the PSCX 2.0 is xcopy. There is no installer. For more more info, check out the releases notes on the PSCX 2.0 download page. Please let us know if you find any issues. NOTE: Be sure to Unblock the ZIP before extracting it to your Modules folder.
One issue with PowerShell providers is that only the Filesystem and Registry providers’ hierarchies are easily viewable using Windows Explorer and Regedit respectively. The other PowerShell providers are mostly without such a view. This is problematic for providers that supply configuration information like the WSMan provider. Without a lot of cd’ing around it is hard to get a “lay of the land” with respect to the various settings that are available.
In Pscx 2.0, we set out to help with this problem by providing the equivalent of the old DOS tree.com routine. The command is called Show-Tree. If you have the final PSCX 2.0 bits installed try this from an elevated prompt:
C:\PS> Show-Tree wsman: -ShowLeaf
WSMan:\
└──localhost
├──MaxEnvelopeSizekb
├──MaxTimeoutms
├──MaxBatchItems
├──MaxProviderRequests
├──Client
│ ├──NetworkDelayms
│ ├──URLPrefix
│ ├──AllowUnencrypted
│ ├──Auth
│ │ ├──Basic
│ │ ├──Digest
│ │ ├──Kerberos
│ │ ├──Negotiate
│ │ ├──Certificate
│ │ └──CredSSP
│ ├──DefaultPorts
│ │ ├──HTTP
│ │ └──HTTPS
│ └──TrustedHosts
├──Service
│ ├──RootSDDL
│ ├──MaxConcurrentOperations
│ ├──MaxConcurrentOperationsPerUser
│ ├──EnumerationTimeoutms
│ ├──MaxConnections
│ ├──MaxPacketRetrievalTimeSeconds
│ ├──AllowUnencrypted
│ ├──Auth
│ │ ├──Basic
│ │ ├──Kerberos
│ │ ├──Negotiate
│ │ ├──Certificate
│ │ ├──CredSSP
│ │ └──CbtHardeningLevel
│ ├──DefaultPorts
│ │ ├──HTTP
│ │ └──HTTPS
│ ├──IPv4Filter
│ ├──IPv6Filter
│ ├──EnableCompatibilityHttpListener
│ ├──EnableCompatibilityHttpsListener
│ └──CertificateThumbprint
├──Shell
│ ├──AllowRemoteShellAccess
│ ├──IdleTimeout
│ ├──MaxConcurrentUsers
│ ├──MaxShellRunTime
│ ├──MaxProcessesPerShell
│ ├──MaxMemoryPerShellMB
│ └──MaxShellsPerUser
├──Listener
│ └──Listener_641507880
│ ├──Address
│ ├──Transport
│ ├──Port
│ ├──Hostname
│ ├──Enabled
│ ├──URLPrefix
│ ├──CertificateThumbprint
│ ├──ListeningOn_1770022257
│ ├──ListeningOn_1480808289
│ ├──ListeningOn_4173834
│ ├──ListeningOn_228071226
│ ├──ListeningOn_1414502903
│ ├──ListeningOn_222657401
│ ├──ListeningOn_616175923
│ ├──ListeningOn_783876958
│ ├──ListeningOn_704030885
│ ├──ListeningOn_405255605
│ └──ListeningOn_1490189075
├──Plugin
│ ├──Event Forwarding Plugin
│ │ ├──xmlns
│ │ ├──Name
│ │ ├──Filename
│ │ ├──SDKVersion
│ │ ├──XmlRenderingType
│ │ ├──lang
│ │ ├──InitializationParameters
│ │ └──Resources
│ │ └──Resource_1958972577
│ │ ├──ResourceUri
│ │ ├──SupportsOptions
│ │ ├──ExactMatch
│ │ ├──Capability
│ │ └──Security
│ │ └──Security_371857150
│ │ ├──Uri
│ │ ├──ExactMatch
│ │ ├──Sddl
│ │ └──xmlns
...
You can use this on most providers although some providers behave better than others under this command. Here’s a way to view registry entries e.g.:
C:\PS> Show-Tree HKLM:\SOFTWARE\Microsoft\.NETFramework -Depth 2 -ShowProperty
HKLM:\SOFTWARE\Microsoft\.NETFramework
├──Property: COMPLUS_HACK_DisableSideBySide = 1
├──Property: DbgJITDebugLaunchSetting = 16
├──Property: DbgManagedDebugger = "C:\Windows\system32\vsjitdebugger.exe" PID %d APPDOM %d EXTEXT "%s" EVTHDL %d
├──Property: Enable64Bit = 1
├──Property: InstallRoot = C:\Windows\Microsoft.NET\Framework64\
├──AssemblyFolders
│ ├──Property: SubKeyCount = 10
│ ├──Property: ValueCount = 0
│ ├──ADOMD.Client 10.0
│ │ └──Property: (default) = C:\Program Files\Microsoft.NET\ADOMD.NET\100\
│ ├──Microsoft .NET Framework 3.5 Reference Assemblies
│ │ └──Property: (default) = C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\
│ ├──MSDeploy
│ │ └──Property: (default) = C:\Program Files\IIS\Microsoft Web Deploy\
│ ├──SQL Server Assemblies
│ │ └──Property: (default) = C:\Program Files\Microsoft SQL Server\100\SDK\Assemblies\
│ ├──SSIS Connection Managers 100
│ │ └──Property: (default) =
│ ├──SSIS ForEach Enumerators 100
│ │ └──Property: (default) = C:\Program Files\Microsoft SQL Server\100\DTS\ForEachEnumerators
│ ├──SSIS Pipeline Components 100
│ │ └──Property: (default) = C:\Program Files\Microsoft SQL Server\100\DTS\PipelineComponents\
│ ├──SSIS Tasks 100
│ │ └──Property: (default) = C:\Program Files\Microsoft SQL Server\100\DTS\Tasks
│ ├──v3.0
│ │ ├──Property: <IncludeDotNet2Assemblies> = 1
│ │ └──Property: All Assemblies In = C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\
│ └──v3.5
│ ├──Property: <IncludeDotNet2Assemblies> = 1
│ └──Property: All Assemblies In = C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\
├──NGenQueue
│ ├──Property: SubKeyCount = 2
│ ├──Property: ValueCount = 0
│ ├──WIN32
│ │ ├──Property: SubKeyCount = 2
│ │ └──Property: ValueCount = 0
│ └──WIN64
│ ├──Property: SubKeyCount = 2
│ └──Property: ValueCount = 0
...
Give PSCX 2.0 a try and let us know what you think - good or bad!
Some questions are coming up about the contents of PSCX 2.0. Here’s what in it:
CMDLETS:
Add-PathVariable
Clear-MSMQueue
ConvertFrom-Base64
ConvertTo-Base64
ConvertTo-MacOs9LineEnding
ConvertTo-Metric
ConvertTo-UnixLineEnding
ConvertTo-WindowsLineEnding
Convert-Xml
Disconnect-TerminalSession
Expand-Archive
Export-Bitmap
Format-Byte
Format-Hex
Format-Xml
Get-ADObject
Get-AdoConnection
Get-AdoDataProvider
Get-AlternateDataStream
Get-Clipboard
Get-DhcpServer
Get-DomainController
Get-DriveInfo
Get-EnvironmentBlock
Get-FileTail
Get-FileVersionInfo
Get-ForegroundWindow
Get-Hash
Get-HttpResource
Get-LoremIpsum
Get-MountPoint
Get-MSMQueue
Get-OpticalDriveInfo
Get-PathVariable
Get-PEHeader
Get-Privilege
Get-PSSnapinHelp
Get-ReparsePoint
Get-ShortPath
Get-TabExpansion
Get-TerminalSession
Get-TypeName
Get-Uptime
Import-Bitmap
Invoke-AdoCommand
Invoke-Apartment
Join-String
New-Hardlink
New-Junction
New-MSMQueue
New-Shortcut
New-Symlink
Out-Clipboard
Ping-Host
Pop-EnvironmentBlock
Push-EnvironmentBlock
Read-Archive
Receive-MSMQueue
Remove-AlternateDataStream
Remove-MountPoint
Remove-ReparsePoint
Resolve-Host
Send-MSMQueue
Send-SmtpMail
Set-BitmapSize
Set-Clipboard
Set-FileTime
Set-ForegroundWindow
Set-PathVariable
Set-Privilege
Set-VolumeLabel
Skip-Object
Split-String
Start-TabExpansion
Stop-TerminalSession
Test-AlternateDataStream
Test-Assembly
Test-MSMQueue
Test-Script
Test-UserGroupMembership
Test-Xml
Unblock-File
Write-BZip2
Write-Clipboard
Write-GZip
Write-Tar
Write-Zip
FUNCTIONS:
Add-DirectoryLength
Add-ShortPath
Dismount-VHD
Edit-File
Edit-HostProfile
Edit-Profile
Enable-OpenPowerShellHere
Get-ChildItem
Get-Help
Get-PropertyValue
Get-ScreenCss
Get-ScreenHtml
Get-ViewDefinition
help
Invoke-BatchFile
Invoke-Elevated
Invoke-GC
Invoke-Method
Invoke-NullCoalescing
Invoke-Reflector
Invoke-Ternary
less
Mount-VHD
New-HashObject
Out-Speech
prompt
QuoteList
QuoteString
Resolve-ErrorRecord
Resolve-HResult
Resolve-WindowsError
Search-Transcript
Set-LocationEx
Set-ReadOnly
Set-Writable
Show-Tree
Stop-RemoteProcess
ALIASES:
Name Definition
---- ----------
?: Invoke-Ternary
?? Invoke-NullCoalescing
call Invoke-Method
cvxml Convert-Xml
e Edit-File
ehp Edit-HostProfile
ep Edit-Profile
fhex Format-Hex
fxml Format-Xml
gcb Get-Clipboard
gpv Get-PropertyValue
gtn Get-TypeName
igc Invoke-GC
ln New-HardLink
lorem Get-LoremIpsum
nho New-HashObject
ocb Out-Clipboard
ql QuoteList
qs QuoteString
Resize-Bitmap Set-BitmapSize
rf Invoke-Reflector
rver Resolve-ErrorRecord
rvhr Resolve-HResult
rvwer Resolve-WindowsError
skip Skip-Object
sls Select-String
sro Set-ReadOnly
srts Search-Transcript
su Invoke-Elevated
swr Set-Writable
tail Get-FileTail
touch Set-FileTime
PROVIDERS:
AssemblyCache
DirectoryServices
PscxSettings
FeedStore
If you are dealing with an native executable that outputs UTF8 with no BOM (byte order marker) you will find that PowerShell garbles the input. This is most likely an issue with how the .NET console code interprets the incoming byte stream. Without a BOM it isn’t exactly easy to determine the proper encoding for a stream of bytes. For example take the following simple native exe source code that is supposed to output this (BTW ignore the fact that the text says ‘ASCII’ – it is really UTF8):
ASCII outputᾹ
Contents of stdout.cpp:
If you pipe the output of this program into a PSCX utility called Format-Hex (alias is fhex), you can see the actual unicode byte stream that was created by .NET’s interpretation of the incoming byte stream.
PS> .\stdout.exe | fhex Address: 0 1 2 3 4 5 6 7 8 9 A B C D E F ASCII -------- ----------------------------------------------- ---------------- 00000000 41 00 53 00 43 00 49 00 49 00 20 00 6F 00 75 00 A.S.C.I.I. .o.u. 00000010 74 00 70 00 75 00 74 00 DF 00 5B 25 63 25 t.p.u.t...[%c%
You might think you could pipe the output to Out-File –Encoding Utf8 but by the time the .NET strings hit the Out-File cmdlet the damage is already done. As can be seen if you view the subsequent output in Notepad.exe:
ASCII outputᾹ
The solution to this problem is to provide a hint to the .NET console functionality about the encoding of the incoming bytes. You can do this very simply:
PS> [System.Console]::OutputEncoding = [System.Text.Encoding]::UTF8
And it works:
If you open good.txt in notepad you get:
ASCII outputᾹ
And is as it should be. However this may seem somewhat counter-intuitive (it did to me) since the problem is with PowerShell/.NET “reading” console input and not writing it. Well one of the good folks on the PowerShell team pointed out to me that the Console OutputEncoding is probably inherited by child processes and a quick little experiment reveals this to be true. So by setting the OutputEncoding this determines how .NET encodes console output which helps PowerShell/.NET determine the correct encoding when reading in this information via console input.
One last point on this approach is that you should stash the original value of [Console]::OutputEncoding and restore it after you’ve run a problematic exe like in this example. I’ve found that the C# compiler will crash if you run it in PowerShell with the [Console]::OutputEncoding set to UTF8.