May 7 2011

Powershell Magic and Syncing Your Profile through THE CLOUD

Category: PowerShellMatt @ 01:05

Powershell is a wonderful tool.  It replaced cmd.exe as my go-to shell long ago, and I’m consistently finding ways to automate painful, manual processes through simple, clean Powershell scripting.  One thing that bugged me though is that my Powershell profile was not consistent across all my many workstations.  By combining Dropbox and a little Poweshell scripting, I have pushed my Powershell profile into the cloud, and I now have a consistent Powershell experience across all of my development boxes.

The Magic of Powershell

If you are still using cmd.exe on Windows, You’re Doing It Wrong.  Unlike cmd.exe and it’s DOS-era capabilities, Powershell is a modern, robust, and very powerful shell (hence the name!)  You can script out complicated tasks quite easily, and since it’s built on .NET, you have full access to a wide array of .NET functionality.  What really sets Powershell apart though is that it operates on objects instead of text.  When you run “dir *.*” in cmd.exe, the output is simply a string listing of the directory’s contents.  In Powershell, however, what you actually get back are System.IO.FileInfo and System.IO.DirectoryInfo objects!

image

As you can imagine, this opens up some very interesting possibilities.

In addition to all this built-in functionality, there are community extensions that add additional capabilities.  My favorite is the Powershell Community Extensions (PSCX) module.  With it, you can do things like issue web requests:

image

You can also launch processes with elevated permissions:

image

Visual Studio 2010 and Powershell

Visual Studio 2010 (and all recent versions of Visual Studio, for that matter) ship with a shortcut that will launch cmd.exe and configure path variables so that you can easily access Visual Studio tools from the command line.  Since I mainly do development work, I always want Visual Studio tools to be accessible when I launch Powershell.  This is quite easy to achieve, as described by ‘Andy S’ on Stackoverflow.  I’ll take it a step further though and show you how to always have Visual Studio commands available on any of your development machines, regardless of where you’ve installed Visual Studio.

Putting Your Powershell Profile In The Cloud

I’ve mentioned before how found I am of Dropbox.  If you don’t have an account, go get one now, and get it running on all of your machines.  Dropbox is how the cloud should work: you shouldn’t even realize you’re using it. 

Let’s outline what we’re going to do.  First, we’ll create a simple wrapper Powershell profile that will run whenever we launch an instance of Powershell.  In our wrapper profile, we’ll call out to a our real Powershell profile script that we’ve stored in the cloud via Dropbox.  From here, we’ll load up Powershell Community Extensions, find where Visual Studio is installed and configure our environment variables appropriately, and finally apply any other customizations we want.  We’ll even create a stand-alone script we can run to automate the creation of our wrapper Powershell profile on each of our development boxes. 

As a prerequisite step, be sure you’ve configured Powershell so that it allows script execution.  Out of the box, Powershell is locked down and won’t allow you to run scripts.  You can fix this by opening an elevated instance of Powershell and running the following command:

Set-ExecutionPolicy RemoteSigned

image

With Dropbox installed, we’re ready to begin.  You may want to read up on Powershell profiles if you’re curious.  We’ll create a profile that will only affects our user and only loads when we run the official Microsoft Powershell… uhh… shell.  Create and open the file ‘%UserProfile%\My Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1’  This wrapper profile script will contain a single line of code to invoke our real Powershell profile script that we’ll store in Dropbox:

. "C:\Users\YOUR_NAME_HERE\Documents\My Dropbox\Powershell\MyProfile.ps1"

Yup, that’s really all there is to your local profile script.  The ‘.’ operator tells Powershell “run the following script in my current scope”, so any variables, functions, etc. the script adds apply to the session (which is what we want since that’s our real profile script). Be sure you replace YOUR_NAME_HERE with whatever your username is. The path needs to point to a folder (which we’re about to create) in your Dropbox.  If you installed Dropbox and chose to create your Dropbox at a different location on your machine, be sure you update your path accordingly. 

If we start Powershell right now, we’ll get an error because the profile script in our Dropbox doesn’t exist yet.  Let’s create it now.  Make a new ”Powershell” folder in your Dropbox, and create a Powershell script named “MyProfile.ps1”.  While you’re at it, go ahead and download the latest Powershell Community Extensions and extract them to your Powershell folder.  Since there are other modules I plan to include in my profile in the future, I actually made a Modules folder and put the PSCX folder there.  Your folder should look something like this:

image

We should be able to launch Powershell without error now, but it won’t actually do anything since our profile script is empty.   Let’s fix that:

#This is where the Visual Studio 2010 cmd.exe batch script is located. 
$VisualStudioDir = resolve-path "$($env:VS100COMNTOOLS)..\..\VC"

#This is where our shared profile script is located
$MyPath = (split-path $MyInvocation.MyCommand.Path)

pushd “$VisualStudioDir”
cmd /c "vcvarsall.bat&set" |
foreach {
  if ($_ -match "=") {
    $v = $_.split("="); set-item -force -path "ENV:\$($v[0])"  -value "$($v[1])"
  }
}
popd
write-host "`nVisual Studio 2010 Command Prompt variables set." -ForegroundColor Yellow

pushd $MyPath

#Import Powershell Community Extensions
Import-Module .\Modules\pscx

#Now we can use wget to make web requests.  It's not exactly the same as
#the real wget, but it's close enough. 
new-alias -name wget -value Get-HttpResource

popd

Our profile uses an environment variable to find where Visual Studio 2010 is installed.  From this, we can find the vcvarsall.bat script that will configure our environment with access to Visual Studio tools.  Next, we import the Powershell Community Extensions module.  The last bit, the ‘new-alias’ command, is not strictly needed.  It simply adds an alias for PSCX’s Get-HttpResource command so that I can call it using the familiar ‘wget’.   Feel free to make any other customizations you want to your profile at this point.  I recommend running ‘help about_Profiles’ if you want to see what sorts of things you can customize.

Wrapping Things Up

You are all set!  When you launch Powershell, you should see something like this:

image

I decided to take things one step further and automate the creation of my local wrapper profile script, that way I can run a single command to configure my profile on a new machine.  Create a Setup.ps1 script in the Powershell folder on your Dropbox with the following contents:

#This script will *OVERWRITE* your existing powershell profile!!!
#It is used to keep your PSH environment in sync through the cloud.

#This grabs the path to the current script, which we'll use to build up the 
#path to our profile in Dropbox. 
$MyPath = (split-path $MyInvocation.MyCommand.Path)

#Create the local Powershell profile script if it doesn't already exist.
if (!(Test-Path $profile)) {
    New-Item -ItemType File -Force $profile
}

#Have the local profile script invoke the shared profile script in Dropbox. 
"
. `"$MyPath\MyProfile.ps1`"
" > $profile

When you want to use your cloud-based profile on a new machine, invoke ‘Setup.ps1’ from your Dropbox, restart Powershell, and start enjoying your consistent environment!

 

And there you have it!  Powershell is aptly named, and you really should be using it instead of cmd.exe.  And thanks to the magic of Dropbox and cloud-backed storage, you can easily synchronize your profile between your personal workstation, your laptop, your company workstation, and any number of other boxes you use.  Give it a shot today, and please let me know if you have any additional tips or tricks I should try!

Tags:

Feb 16 2011

Setting Certificate Permissions With Powershell

Category: PowerShellMatt @ 01:23

This post is more a reminder for me than anything else, but you may find it useful if you are scripting certificate-management tasks on Windows.

The Certificate Manager snap-in for Windows allows you to manage permissions for certificates.  This is important if you want to, for example, grant your ASP.NET application the ability to use a private key from the certificate store in order to perform encryption/decryption operations.  While it is easy to script out many certificate-related operations with the makecert and other command-line tools from Microsoft, I failed to find anything that would allow me to change actual certificate permissions. 

Did you know that Windows actually keeps the private keys for your certificates on the filesystem?  Yeah, I didn’t either.  It turns out that you can simply change permissions on the private key file to change permissions for the certificate itself.  This is easy to do using the Icacls tool.

Unfortunately, it is not so easy to determine which key file corresponds to which key in your certificate store.  The key files are buried under the C:\Users directory and have intuitive names like  8aeda5eb81555f14f8f9960745b5a40d_38f7de48-5ee9-452d-8a5a-92789d7110b1.  I haven’t found a built-in way to figure out which file belongs to which key, but there is a sample tool available from Microsoft that can help: FindPrivateKey.exe, which you can download from Microsoft Download Center as part of the WCF and WF samples pack.  The samples are all in source form, so you’ll need to compile the tool using Visual Studio.

Once you have compiled the tool, the following Powershell script will find the path to your private key and set the permissions so it can be read by IIS:

$keyName = "YourKeyNameHere"

$keyPath = .\FindPrivateKey My LocalMachine -n "CN=$keyName" -a

icacls $keyPath /grant "IIS_IUSRS:(F)"

Simple enough, right? 

Tags:

Feb 7 2011

A Cross-Version PowerShell Function To Restart IIS App Pools

Category: PowerShellMatt @ 23:51

The web is littered with different ways to restart IIS application pools.  Unfortunately, no one method that I’ve found works consistently across both IIS 6 and II7.  In this simple snippet, you can see the simple function I crafted that works across both current versions of IIS.

function Restart-IISAppPool {
    param(
        $PoolName
    )
    
    if (Test-Path c:\Windows\system32\inetsrv\appcmd.exe) {
        Write-Host "Restarting AppPool on IIS7..."
        c:\windows\system32\inetsrv\appcmd stop apppool /apppool.name:$PoolName
        c:\windows\system32\inetsrv\appcmd start apppool /apppool.name:$PoolName
    }
    else {
        Write-Host "Restarting AppPool on IIS6..."
        $appPool = get-wmiobject -namespace "root\MicrosoftIISv2" -class "IIsApplicationPool" | Where-Object {$_.Name -eq "W3SVC/APPPOOLS/$PoolName"}
        $appPool.Recycle()
    }
}

You can use the function like so:

Restart-IISAppPool "MyAppPool"

Enjoy!

Tags:

Mar 30 2010

Using Powershell to apply new C# field naming conventions

Category: PowerShellMatt @ 02:34

I recently found myself wanting to rename all the fields in several large projects to conform to a new naming scheme.  Sadly Resharper was not helpful here, so I turned to a combination of Regex and Powershell. 

Our team has recently revised our 7 year-old coding guidelines.  One of the guidelines that was changed concerned field naming.  Our previous guideline stated that fields should use an ‘m’ prefix with Pascal Casing, creating fields of the form “mFieldName”.  At the time the guidelines were written, .NET was still young, and the community hadn’t yet settled on a widely accepted standard.  Since then, most of the community seems to have gravitated towards using an underscore prefix for fields with camel casing, creating fields of the form “_fieldName”.  We finally decided to bite the bullet and switch our guideline to conform to the more generally accepted convention.  The problem is that we have a lot of code that uses the old style.  By “a lot”, I mean probably in the neighborhood of 100k lines of code across the various projects. 

One solution we discussed was to just leave existing fields alone, and only use the new convention when creating new code.  This would work, but since we use Resharper to enforce the naming conventions, it would create a lot of false warnings.  It also meant that we would be introducing some inconsistency into our code base, which we wanted to avoid.

One avenue that we explored was using Resharper to update all existing field names.  The Patterns functionality introduced in Resharper 5.0 might be able to accomplish this, but I was unable to figure out how. 

When that failed, James suggested that I try using Regex.  It took a bit of Powershell hacking, but I managed to come up with a script that worked.  I’ve applied it across thousands of classes, and it *appears* to rename things correctly.  Here’s the script:

   1: $regex = New-Object System.Text.RegularExpressions.Regex "(?:\W(?<name>m[A-Z][a-zA-Z0-9_]+))"
   2: Get-ChildItem * -Recurse -Include *.cs | ? {$_ -notmatch 'Resharper' } | ? {$_ -notmatch 'DecompilerCache' } | ForEach-Object { 
   3:     $text = [System.IO.File]::ReadAllText($_.FullName)
   4:     $matches = $regex.Matches($text)
   5:     $matches | ForEach-Object { 
   6:         $toReplace = $_.Groups["name"].Value 
   7:         $newValue = "_" + [System.Char]::ToLower($toReplace[1]) + $toReplace.Substring(2)
   8:         $text = [System.Text.RegularExpressions.Regex]::Replace($text, "(?:(?<prefix>\W)" + $toReplace + ")", "`${prefix}$newValue")
   9:     } 
  10:     [System.IO.File]::WriteAllText($_.FullName, $text)
  11: }

The first line creates a new Regex object that will match things of the form “mFieldName” that are prefixed by a non-word character.  Next, the script recursively iterates through C# files, ignoring the contents of the Resharper directory as well as the DecompiledSources directory.  For each matched file, the script grabs the full text from the file, finds the field names that need to be changed (if any), then uses a different regex to replace the field name in the text.  Note that simple string.Replace won’t work here since the replacement must account for the non-word character that prefixes the filed name, otherwise things such as “NumWhatever” will be transformed as well (as I found out with an earlier version of this script).  Finally, the modified text is written back to the file.

It’s not terribly pretty, and it certainly isn’t innovative, but it did save me quite a bit of time this morning. :)

Tags:

Feb 1 2010

The trials and tribulations of using MSDeploy with PowerShell

Category: Deployment | PowerShell | MSDeployMatt @ 09:21

I can sum my experience with trying to use MSDeploy and Powershell together with a single word: hell.  MSDeploy.exe does not play nicely with PowerShell, but thanks to some help from James and a lot of trial-and-error, I’ve got it sort-of working now.  Here’s a tail of my journey.  Hopefully you, brave reader, will learn from my mistakes.  THAR BE DRAGONS HERE.

It began with the automation of deployments

This journey started from a desire to automate our deployment processes.  We have three different software applications that we host under a SaaS model, and only one of those has a deployment that even remotely qualifies as semi-automated.  Making deployment less painful typically leads to more frequent deployments and happier developers, so I set out to automate things.  I have previously used MSBuild and PowerShell to automate things, but that was several years ago, and the tools have changed.  Today, we have a new tool to help with deploying web applications: the Microsoft Web Deployment Tool, or MSDeploy. FTA:

The Web Deployment Tool simplifies the migration, management and deployment of IIS Web servers, Web applications and Web sites.

Basically, MSDeploy gives us some great functionality for remote deployments.  With it, I can deploy files, execute commands remotely, run SQL scripts, etc.  On paper, this seemed like a great tool.  Using it, I should be able to deploy applications without having to log on to the target servers at all

So, with MSDeploy installed and ready, I decided I would use PowerShell to drive things instead of MSBuild or batch scripting.  After all, PowerShell is the future of the Windows Shell. And MSDeploy is new hawtness.  So the two will surely play nice together, right?  Right?

You seek the 2.0 shell?

Before starting, I decided I would install PowerShell 2.0.  It was required for one of the tools I was using (which I’ll cover in a future post).  I was running Vista with PowerShell 1.0 installed.  So I hit up Google to find the 2.0 release:

PowerShellGoogle

Hmm, none of the top results looked like a download page for the final version.  I didn’t want a CTP, I wanted the final version.  Bing was not much better:

PowerShellBing

Bing returned a blog post indicating that the RTW version shipped, but no link in the first few results (yes, I am one of those searchers who only scans the first few hits before abandoning my query and trying a different one).  I finally found the PowerShell portal on TechNet, which did include a link to download PowerShell 2.0.  PS2 is part of the Windows Management Framework, which is intuitively* disguised as KB968929. Suggestion to Microsoft: how about making it a little less difficult to find PowerShell?

*SarcasmLevel = int.MaxValue;

There can be no alliance between PS and MSDeploy

Despite the initial frustration of finding and installing the latest PowerShell, I had high hopes that things were going to go smoother now.  I had numerous examples of how to use MSDeploy.exe from the command-line, and the documentation actually seemed pretty solid.  You can imagine my surprise when I tried running a simple example and was greeted by the following:

powershellFail

Note: The comments are mine, not MSDeploy output.  There is no MSDeploy output.  Nothing.  At all.  Until I forcibly end msdeploy, I can type whatever I want, and it just sits there quietly.

I’m not a PowerShell guru, but I don’t think that is normal behavior.  I found a handful of blog and message forum posts scattered across the web reporting similar behavior when using MSDeploy with PowerShell.  After reading the IIS blog, I started to suspect that using MSDeploy with PowerShell, at least through the command line, was actually not supportedThis really dashed my hopes:

And let us know if you want to use Web Deploy with PowerShell, we're interested in hearing about what tasks you'd like to accomplish and why!

I bet the use cases for MSDeploy with PowerShell are the same as using MSDeploy from the old CMD shell.  I’m really surprised that PowerShell is apparently not fully supported right out of the box.  I think Microsoft did realize that people might want to use the tools together. Why else would they have had PowerShell cmdlets in the early preview releases?  Unfortunately, these cmdlets were removed from the final version (see the last comment).

More digging around led me to some actual working examples of using MSDeploy with PowerShell, but it seemed a lot less elegant than running msdeploy.exe. 

An old friend comes to the rescue!

Despite this setback, I wasn’t ready to give up yet.  My initial solution was to wrap msdeploy.exe in a (very) simple batch script that I could invoke from PowerShell, but James gave me a better solution: invoke msdeploy.exe through cmd.exe directly!  I created a function to wrap the invocation, like so:

function PushToTarget([string]$server, [string]$remotePath, [string]$localPath) {
    cmd.exe /C $("msdeploy.exe -verb:sync -source:contentPath=`"{0}`" -dest:computerName=`"{1}`",contentPath=`"{2}`" -whatif" -f $localPath, $server, $remotePath )
}

Now I can use MSDeploy to sync files to my production servers.  In a future post, I’ll show you how capability fits in to our larger deployment process.

Tags:

Jul 29 2009

Merging and minimizing JavaScript files with YUI Compressor and PowerShell

Category: PowerShell | JavaScriptMatt @ 07:26

One of the tenants of liteGrid is that it’s modular, with the core doing as little as possible, and all the richness being layered on top by various pluggable modules.  These modules are currently spread across multiple files (one per module).  As liteGrid gets closer to “production,” it became time to merge things into a single file (no one wants to include 17 JS files).  I also wanted to minify/minimize the scripts to insure faster downloads.  The YUI Compressor is a good command-line tool for compressing JavaScript and CSS, and it turns out that it can actually be used to merge files, too.  I wrote a simple one-line PowerShell script that does all the magic.  Get ready for it… here it is:

gc *.js | java -jar yuicompressor-2.4.2.jar --type js -o liteGrid.min.js

The script assumes that “java” is in your path and that yuicompressor is in the current directory.  Enjoy!

I’ll get back to writing longer (better?) blog posts next week, this week is filled with deadlines and other not-fun stuff.

Update: Rookie mistake: you want to be sure you delete the minimized file before regenerating it, otherwise it’s old contents will be pulled in as well (not good).

Tags:

Dec 5 2008

Setting Directory Permissions with Windows PowerShell

Category: PowerShellMatt @ 09:41

In my on-going attempts to simplify the deployment of our web application here at Day Job, I have created a PowerShell script that sets the permissions on all the directories used by the app.  It's amazing how much simpler something like this is in PowerShell compared to VBScript.   Here's some code that will give the NetworkService user read-access to a directory (useful for data files that your web app needs to access):

   1: #These constants are used to set permissions
   2: $inherit = [system.security.accesscontrol.InheritanceFlags]"ContainerInherit, ObjectInherit"
   3: $propagation = [system.security.accesscontrol.PropagationFlags]"None"
   4:  
   5: #Set directory permissions
   6: $directory = "DataFiles"
   7: $acl = Get-Acl $directory
   8: $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("NetworkService", "ReadAndExecute", $inherit, $propagation, "Allow")
   9: $acl.AddAccessRule($accessRule)
  10: Set-Acl -aclobject $acl $directory

You need to pass in enumeration members to insure that the permission change is applied correctly, so the first block of code just grabs the needed values from the appropriate enumerations.  With those in hand, all you have to do is use the FileSystemAccessRule class to add a new rule, then apply it to the directory. 

One tricky thing I ran into was handling the Internet Guest Account.  Recall from my WiX ordeals that IIS 7.0 systems (Vista and Windows 2008) use IIS_USRS while IIS 6 systems use IUSR_[MachineName].  I couldn't come up with an elegant way to handle it, but this hackery does the trick:

   1: $computerName = (Get-WmiObject win32_computersystem).name
   2: #This determines which user is the guest user for IIS.  Windows Vista and 08 use the IIS_USRS group, Previous version use 
   3: #IUSR_[MachineName]
   4: if ([environment]::osversion.Version.Major -eq 6) {$webUser="IIS_IUSRS"} else {$webUser="IUSR_" + $computerName}
   5:  
   6: $directory = "Web"
   7: $acl = Get-Acl $directory
   8: $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("NetworkService", "ReadAndExecute", $inherit, $propagation, "Allow")
   9: $acl.AddAccessRule($accessRule)
  10: $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($webUser, "ReadAndExecute", $inherit, $propagation, "Allow")
  11: $acl.AddAccessRule($accessRule)
  12: Set-Acl -aclobject $acl $directory

The above snippet Grants the NetworkService and IIS guest account read permissions on the Web directory, which will allow our ASP.NET located in Web to execute once the virtual directory is created in IIS (which I'll describe how to do just as soon as I figure it out).

Tags: