Keith's profileKeith Hill's BlogPhotosBlogListsMore ![]() | Help |
|
|
October 21 Windows PowerShell 2.0 String LocalizationOne 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.psd1 This 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.psd1 But 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.ps1 The output of this script is: PS> .\test.ps1 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.psd1 At this point our dir structure looks like this: .\test.ps1 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 } 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 } 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! It’s Official - Windows 7 and Windows PowerShell 2.0 for Everyone!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. October 16 Windows PowerShell 2.0 Virtual Launch PartyI’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.
October 12 Windows PowerShell 2.0 and Windows Troubleshooting Platform PresentationTonight 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. July 17 A Simple Go Function to Simplify Navigating to Popular DirectoriesBefore PowerShell came along I had a number of doskey aliases set up like “gt” to go to the temp dir. I have since converted those aliases to a single g function (short for “go”): 1: function g($shortcut) { 2: $ht = @{3: bin = "C:\Bin"; 4: doc = "$([Environment]::GetFolderPath('MyDocuments'))"; 5: sys = "$env:SystemRoot\System32"; 6: t = "$env:Temp"; 7: win = "$env:SystemRoot"; 8: cf = "$env:CommonProgramFiles"; 9: pf = "$env:ProgramFiles"; 10: www = "$env:SystemDrive\inetpub\wwwroot"; 11: gac = "$env:SystemRoot\Assembly\GAC"; 12: tfs = "C:\Tfs" 13: clr = "$([System.Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())"; 14: } 15: 16: if ($shortcut) { 17: if (!$ht["$shortcut"]) { 18: throw "$shortcut isn't valid shortcut. Execute g with no params to see valid shortcuts." 19: } 20: cd $ht.$shortcut 21: }22: else { 23: "Valid shortcuts are:" 24: $ht.Keys | Sort | select @{n='Shortcut';e={$_}},@{n='Destination';e={$ht.$_}} 25: } 26: }
Usage is pretty simple. Execute g to see all shortcuts and the associated location: PS C:\> g Shortcut Destination To set-location to the wwwroot dir execute the following: PS C:\> g www It’s a trivial function but if you spend a lot of time at the PowerShell prompt, it (or something similar) is quite handy. May 02 PowerShell V2 Remoting on Workgroup Joined Computers – YES It Can Be DoneThere are a number of extra steps to take to get V2 remoting to work on workgroup joined computers like the ones in your home – unless you’re running a DC at home (sick puppy). First up, is a registry setting that makes V2 remoting work on workgroup computers: PS> new-itemproperty -path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System -name LocalAccountTokenFilterPolicy -propertyType DWord -value 1 Be sure to run this from an elevated prompt. Then from that same elevated prompt, execute the following cmdlet: PS> Enable-PSRemoting To verify that remoting is working on this PC by executing the following command (also from an elevated prompt): PS> Enter-PSSession localhost If remoting is working you should get a prompt something like this: [localhost]: PS C:\Users\Keith\Documents> Note: If you are trying to get PowerShell 2.0 remoting working on an XPMode virtual machine (you know the one you get for free with Windows 7 Pro or higher) then you need to enable Classic share & security model for local accounts like so:
Now try re-running Enable-PSRemoting and it should work. Finally, on the PC(s) that you want to use to initiate remoting, execute this command so that all the local target computers are trusted: PS> set-item wsman:localhost\client\trustedhosts -value * Now, at this point you should be able to enter a new pssession to a remote computer. Note that you don’t have to be in an elevated prompt to do this. However you will need to pass your credentials to the remote computer like so: PS> $cred = Get-Credential # Type in the username/password for an admin account on the remote PC Credential Delegation One other area that can bite you is credential delegation. Here is the scenario. Say you remote into a PC and from that PC you want to access files on another PC via a UNC share. As things stand now, you run into the following error: [mediacenterpc]: PS C:\Users\Keith\Documents> dir \\homeserver\photos To fix this you have to enable credential delegation (second hop) and use CredSSP authentication. First on the target/remote computer you need to run this in an elevated prompt: Note that you can’t run this from a V2 remoting session (you’ll get “access is denied”): PS> Enable-WSManCredSSP –Role Server Now on your client/local computer execute the following from an elevated prompt for each remote computer you need credential delegation for: PS> Enable-WSManCredSSP –Role Client –DelegateComputer <computer_name> We are close but there is one last step and it requires a tweak via the global policy editor. Run gpedit.msc and navigate to Computer Configuration –> Administrative Templates –> System –> Credential Delegation as shown below: Open up the “Allow Delegating Fresh Credentials with NTLM-only Server Authentication” setting. Enable the setting and then click on the “Show…” button to add a server to the list. I added mine like so: Press OK and then press the “Apply” button on the previous dialog to apply the setting. Now credential delegation will work for that configured remote computer. Note that when you enter a new PSSession you have to use CredSSP authentication as shown below: PS> Enter-PSSession MediaCenterPC -Cred $cred -Authentication CredSSP Directory: \\HomeServer\Software Mode LastWriteTime Length Name Note that from the remoting session on MediaCenterPC, I can now see the files shared from my Windows Home Server. Woohoo! It isn’t exactly as straight forward as I would like but it can be done. April 05 Generating New-Object Wrapper Functions for an AssemblyOne of the best parts of being a Microsoft MVP is having conversations with all the other, really bright MVPs. The PowerShell team just recently added Doug Finke as an MVP – congratulations Doug! It isn’t too uncommon for the PowerShell MVPs to challenge and push one another. It is a great learning experience! Over the weekend, Doug and I engaged in one of these and in the end we came up with a little bit of PowerShell script (V2 required for the generics support) that can automatically generate wrapper functions around the constructors of public .NET types in an assembly. These wrapper functions are strongly typed (well mostly) and support generic types. Here’s the script: param($assemblyName) "@ NOTE: if you copy/paste this script, watch out for whitespace after the here strings’ opening sequence – @” – there shouldn’t be any. If you run it on mscorlib, it will product wrapper functions like so:
Then you can create a generic list like so: PS> $list = New-SystemCollectionsGeneicList –OfT double March 21 PowerShell Function NamesPowerShell allows you to use many different characters in your function names besides [_aA-zZ][_aA-zA0-9], which is the typical regex recipe for function and method identifiers in a fair number of languages. However, in PowerShell you have a much larger palette of characters to choose from e.g.: function ?? ($expr, $default = $(throw "Must specify default value")) { Function names like these can help you create operators for an internal DSL. The primary downside is that the operator has to be used in a pre-fix manner e.g.: PS> ?? ($env:LogDir) $env:temp Things get really interesting when you start to use mathematical symbols. Check this out: PS> function √($num) { [Math]::Sqrt($num) } This is not really practical since the mathematical symbol characters (0x221A and 0x3C6) aren’t easy to type at the console but it shows the extent to which you can use radically different characters to name your functions. psmdtag:script - function name March 15 Customizing PowerShell ISE with Yank Line CustomMenu ItemI was excited to hear that the PowerShell ISE editor was based on the new WPF editor that will ship in Visual Studio 2010. However I was disappointed when I couldn’t find one of my favorite shortcuts in the script editor: Ctrl+L a.k.a. yank line. Fortunately the object model for the editor is capable enough to support this capability. Here’s a pretty simple custom menu entry with an assigned keyboard shortcut (Ctrl+L) to perform a yank line in the editor: 1: [void]$psISE.CustomMenu.Submenus.Add(2: 'Yank Line', 3: { 4: $editor = $psise.CurrentOpenedFile.Editor 5: $line = $editor.CaretLine 6: $lineLen = $editor.GetLineLength($line)7: if ($line -lt $editor.LineCount) 8: { 9: $editor.Select($line, 1, $line + 1, 1) 10: }11: else 12: { 13: $editor.Select($line, 1, $line, $lineLen + 1) 14: } 15: [System.Windows.Clipboard]::SetText($editor.SelectedText)16: $editor.InsertText('') 17: }, 18: 'Ctrl+L' 19: )I find this handy for copying an entire line. I yank the line that I want to copy to the clipboard. Once it is yanked, I can paste it back as many times as I need. psmdtag:ise - CustomMenu January 28 Tail-Content – Better Performance for Grabbing Last Lines From Large (ASCII) Log FilesNecessity is the mother of invention, or in the case of Windows PowerShell, a new script. I have a set of 23 large (28 MB) log files on a remote machine in which I need to verify that the last line of each of them is identical. My first “naive” approach was to do this:
Yeah unfortunately that took so long that I killed it and set out to create a script that would “efficiently” tail a file. Now my log files were ASCII encoded so it made the task much easier. Bottom line is that there is FileStream object in .NET that allows you to start at the end of a file and work backwards. The approach above using Get-Content requires that PowerShell get every single line from a log file with ~1,000,000 lines in it and over the network to boot. With FileStream you can read from the end of the file backwards. Because my files are ASCII, that is very easy to do. So I created a Tail-Content.ps1 script that you can download. Note that it doesn’t work on Unicode encoded files and it doesn’t do active tailing. However, it is very fast for large files. There are a few interesting parts of the code to examine. First, if you want to handle paths like PowerShell does, the snippets below show you how to setup your parameters to handle wildcard expansion and literal paths. This does require that you are on version 2 of PowerShell: 1: [CmdletBinding(DefaultParameterSetName="Path")] 2: param(3: [Parameter(Mandatory=$true, 4: Position=0, 5: ParameterSetName="Path", 6: ValueFromPipeline=$true, 7: ValueFromPipelineByPropertyName=$true)] 8: [string[]] 9: $Path, 10: 11: [Alias("PSPath")] 12: [Parameter(Mandatory=$true, 13: Position=0, 14: ParameterSetName="LiteralPath", 15: ValueFromPipelineByPropertyName=$true)] 16: [string[]] 17: $LiteralPath, 18: 19: <elided> 20: )Note that the default parameter set is “Path” and the Path parameter accepts pipeline input by name and by value. This means that raw strings will work as paths assuming they actually contain valid paths. Also note that both parameters are of type string array. The LiteralPath parameter is defined in a different, mutually exclusive parameter set named “LiteralPath” and it binds to pipeline input only by property name. It is important that we also decorated the LiteralPath parameter with the Alias attribute “PSPath”. This way output of Get-ChildItem (FileInfo) gets bound by property name to the LiteralPath parameter by virtue that PSPath is an alias for the same parameter. This happens because there is no Path property on FileInfo but there is a PSPath property. Remember that PowerShell extends the FileInfo type by adding the PSPath NoteProperty. That sets up the parameters, now here is what you need to do in your Process function to handle Path parameters which could specify paths with wildcards in them: 1: Process 2: {3: if ($psCmdlet.ParameterSetName -eq "Path") 4: {5: # In the non-literal case we may need to resolve a wildcarded path 6: $resolvedPaths = @()7: foreach ($apath in $Path) 8: { 9: $resolvedPaths += @(Resolve-Path $apath | Foreach { $_.Path }) 10: } 11: }12: else 13: { 14: $resolvedPaths = $LiteralPath 15: } 16: 17: foreach ($rpath in $resolvedPaths) 18: { 19: $PathIntrinsics = $ExecutionContext.SessionState.Path 20: 21: if ($PathIntrinsics.IsProviderQualified($rpath)) 22: { 23: $rpath = $PathIntrinsics.GetUnresolvedProviderPathFromPSPath($rpath) 24: } 25: 26: Write-Verbose "<cmdlet-name> processing $rpath" 27: 28: #process file here 29: } 30: }On line 3 I test which ParameterSet is being used. If it is the Path parameter set then we need to resolve the paths specified because they may contain wildcards. I do that on line 9 using Resolve-Path. Then on line 17 we iterate through each path and process it. One other detail that you may or may not need to worry about is that $rpath at this point may contain a provider qualified path e.g. Microsoft.PowerShell.Core\FileSystem::C:\foo.txt. These work fine with PowerShell however if you need to pass this path to a .NET object it won’t recognize that as a valid path. So on line 21 I check to see if we have a provider qualified path and if I do I get the raw path using $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath as shown on line 23. The rest of this script just does low-level byte reads from the end of the file. I went back and measured my original approach of using { Get-Content \\server\share\logs\*_26_03.csv | Select -Last 1} and it took ~13 minutes. Using my Tail-Content script, it took < 1 second. That is a speed up of about 1789x! psmdtag:dotnet: FileStream January 01 Renewed as Windows PowerShell MVP for 2009I got a very nice email today telling me that I have been awarded as a MVP for Windows Server - Admin Frameworks (aka Windows PowerShell). Woohoo! Not sure if I’ll get to make it to the summit this year because of an uncertain job situation but I’m going to try. December 11 Little Things in PowerShell - Determine Timespan of Files in a DirOften it is the "little things" that put a smile on your face. PowerShell does that for me a lot. Take for instance a simple little problem I ran into today while unit testing some code that generates log files. I wanted to know how long it took to write all the log files to disk. Due to the way the code works on a background thread, it wasn't particularly easy to use a System.Diagnostics.Stopwatch to do this. You could do this manually by inspecting the file dates (DateCreated and DateModified) in Windows Explorer but it only shows resolution down to minutes and I have to do the math myself. No good. Well this information is easy to figure out with PowerShell. Just CD into the log dir and execute: PS> $start = dir | foreach {$_.CreationTime} | sort | select -first 1 Days : 0 :-) Yes PowerShell can do all sorts of acrobatics WRT managing and querying a computer but it can also do the simple stuff in a pretty straight forward manner. December 01 Windows PowerShell Training WebcastSign up for some free PowerShell training via this TechNet webcast. Here’s the overview:"
I like these because you usually get some decent info for very little investment and if it doesn’t turn out to be right for you, you can bail easily with very little time lost. November 11 Team Foundation PowerShell PSSnapin in October Team Foundation Power Tools DropOh thank you, thank you TFS team!!! This is a great development because it enables version control queries for mere mortals (ie those of us not well versed in SQL Reporting Services). It also enables easily scriptable queries. With the October Team Foundation Power Tools drop, you get a PowerShell console icon in the start menu or you can just add the following to your profile: Add-PSSnapin Microsoft.TeamFoundation.PowerShell Once you’ve done that, here are the new cmdlets you get:
Now that you have the Team Foundation snapin loaded, you can start doing some interesting stuff like finding out who has pending changes older than 30 days:
Or say you want to see the level of checkin activity over the past 30 days:
If you have the excellent PowerGadgets, then you could do this:
And get this nice graph: Now if I’m a project manager on a large team, I might want to see “who” is checking in a bunch of code right before a deadline. That is easy enough:
Querying is an especially powerful aspect of PowerShell and teamed with these new Team Foundation version control cmdlets, you have access to some interesting information. psmdtag:snapin: Team Foundation Power Tools October 26 Nother PowerShell Convert in the Making?I ran into Ian Griffiths in the PDC bookstore today. .NET smart client devs would probably know him as the author of books on WinForms and WPF programming and course instructor on those topics. He was buying Bruce's Windows PowerShell in Action book which is my favorite PowerShell book. I let Ian know he'd made a great choice and let him know that I think PowerShell is a great tool for developers! October 21 PDC 2008 BoundI’ll be at the PDC next week. I get there Saturday night to attend a Sunday pre-con. If any of you PowerShellers are going to be at the PDC, drop me a line. I’d like to meet you and talk PowerShell. There will be one session on PowerShell at the PDC that Jeffrey Snover will be presenting: PowerShell: Creating Manageable Web Services This session is on Thursday from 8:30 am to 9:45 am in 406A. If you haven’t seen Jeffrey talk, he is a great speaker and passionate advocate of PowerShell (he should be, he’s the primary architect of PowerShell). See you there!
August 25 Windows PowerShell Has Moved Up to #15 on the TIOBE Programming Community IndexThe popularity of PowerShell is shooting through the roof right now. Check out this TIOBE index from August 2008. Notice the delta in position rating on PowerShell. What is that, like 10 up arrows. Impressive. August 14 Error "Cannot delete file: Cannot read from the source file or disk"I got asked this question by someone I can't seem to reply to. Anyway the scenario involves Unix, Macs and PCs writing to the same Samba server. The file attributes indicate readonly, hidden and system. The use of the -force parameter will get PowerShell to display hidden/system files. However the OP reports that Windows Explorer gets the same error. This smells like invalided characters in the path. This Microsoft KB article gives some guidance on this sort of problem in the "Invalid File Names" section. If anyone else has run into this issue specifically, feel free to post a comment on how you eventually removed the offending files. August 02 Finding Which Executables Use A DLL Entry PointRaymod Chen wrote a blog post called "Don't be helpless: ..." where he shows a batch command to start the process of finding which executables use a particular DLL entry point.
The hard work is done by link (dumpbin really) so the script provides the glue code and formatting support. I just had to give this a go in PowerShell where I knew the formatting would be superior plus I wanted to support searching on multiple entry points. Here is the script I came up with: param([string[]]$filter=$(throw "-filter parameter is required")) get-process | select ProcessName -expand Modules -ea 0 | foreach { $dll = $_ link /dump /imports $_.filename | select-string $filter | select @{n='Process';e={$dll.ProcessName}}, @{n='Dll'; e={$dll.ModuleName}}, @{n='Entry'; e={$_.Line.Substring(6)}} } | format-table Dll, Entry -groupby Process The script output looks like this:
Now that is some nice formatting made possible by the Format-Table -GroupBy parameter. May 26 Poor Man's File/Directory Name Indexer Using Windows PowerShellThe following is a script that I have set up on my dev PCs to run nightly via a scheduled task: ## CatalogFileSystem.ps1 My scheduled task is configured like so (and runs everyday at 4 am): Program: C:\Windows\System32\WindowsPowerShell\v1.0\PowerShell.exe I also set this task to run with highest privileges on Vista so it can catalog more of the nooks and crannies of my filesystem. This script creates two files setting at the root of each path supplied. In my case: C:\dirlist.txt Searching for files on my filesystem is now very easy:
In fact I have created a shortcut function for the above I call Find-File (alias ff): Set-Alias ff Find-File And then this sort of filename search gets even easier:
Now you might be wondering why I don't just use the search built into Vista. Well first off, Vista's search doesn't index your whole hard drive or any volumes other than the contents of C: by default. While it can be set up to index more locations I worry about the performance hit because the second issue is that the Vista search indexer runs in the background all the time. The CatalogFileSystem script runs just once a day (or however often you schedule it) when you're typically not using the computer. Yeah, it could be as much as 24 hours out of date but the vast majority of the time I'm searching for files that have been on my system for a while: C++ header files, SDK header files, C runtime source files, windows DLLs, etc. I'm sure some folks are going to think this is goofy but honestly I do search the filelist.txt file quite often (not so much the dirlist.txt file). Do you have any similar convenience scripts like this? If you do, please add a comment and let me know. |
|
|