Softwire Blog


Managing Windows Scheduled Tasks with PowerShell


7 October 2014, by

Previous article in series

I used to think Windows Scheduled Tasks were rubbish. Not something a proper techy would use.

Actually they’re still not perfect, but they’re hugely better than they once were, and a bit of PowerShell magic can make managing them a breeze.

The cutting edge

In typical PowerShell fashion, the latest and greatest functionality is only available in Windows Server 2012 / Windows 8. I haven’t had the pleasure of using this functionality in full myself, but you can take a look at the full reference. Suppose you want to disable all scheduled tasks in a particular folder – it’s easy:

Get-ScheduledTask -TaskPath "\MyTasks\" | Disable-ScheduledTask

You can use similarly straightforward commands to create tasks, schedule them, etc. Deployment made easy.

Only slightly in the dark ages

If you’re stuck back in Windows Server 2008, you can’t do the above. But all is not lost. A cut-down version of the task scheduler cmdlet library is available as part of the PowerShell Pack from the Windows 7 Resource Kit – Microsoft seem to have lost this from their website but I’ve found a copy that’s still available.

This provides a load of handy cmdlets including Get-ScheduledTask – you can see a full list via:

Get-Command -Module TaskScheduler

Sadly there are some omissions, in particular the Disable-ScheduledTask command I mentioned above. As luck would have it this is precisely what I needed to do. In case it helps anyone else, here’s my workaround – in the form of a pair of scripts that disable all tasks in a folder, and then re-enable them (without enabling any that were disabled in the first place).

DisableScheduledTasks.ps1

Import-Module PowerShellPack

"Disabling all scheduled tasks..."

Get-ScheduledTask -Folder MyTasks
  | Where-Object { $_.Status -ne "Disabled" }
  | ForEach-Object {
      Out-File -Append -InputObject $_.Name -FilePath "DisabledTaskList.txt"
    }

If (!(Test-Path "DisabledTaskList.txt")) {
  "No tasks to disable"
  Return
}

Get-Content DisabledTaskList.txt
  | ForEach-Object { schtasks /Change /TN ( "MyTasks\" + $_ ) /DISABLE }
  | Out-Null

while (Get-ScheduledTask -Folder MyTasks
  | Where-Object { $_.Status -ne "Disabled" }) {
      "Waiting for all scheduled tasks to complete..."
      Start-Sleep -Seconds 5
    }

EnableScheduledTasks.ps1

Import-Module PowerShellPack

If (!(Test-Path "DisabledTaskList.txt")) {
  "No tasks disabled - please run DisableAllScheduledTasks first"
  Return
}

"Enabling all scheduled tasks..."

Get-Content DisabledTaskList.txt
  | ForEach-Object { schtasks /Change /TN ( "MyTasks\" + $_ ) /ENABLE }
  | Out-Null

Remove-Item DisabledTaskList.txt

Hopefully this will help you get your task scheduling automated even if you’re not able to use the latest version of Windows!

Configuring IIS with PowerShell


29 September 2014, by

Previous article in series

Building on my last post on installing optional Windows features automatically using PowerShell, here’s something else you’ll often need to do when provisioning a development machine, or indeed a production server: Configuring an IIS website.

I will choose to ignore anything prior to IIS7 and Windows 7 / Server 2008. This starting point has two happy consequences. Firstly, you get all the necessary PowerShell cmdlets to manage IIS built-in. Secondly, you can deploy multiple websites even on a non-Server version of Windows, which means you can more easily follow my first recommendation of automated configuration: Throw it away, and start again.

What’s this about throwing things away?

If you’re configuring something, an important question to ask is what your starting point is, and what this means for your configuration script. In general, building a script that can cope with any possible starting point is difficult. What if you’re configuring IIS, but the website has already been created. Precisely how far through the process did you get previously? Which steps can be repeated? Which steps should be skipped? What if someone’s done some extra steps that you really ought to undo to ensure you end up in a known good state?

Some tools will take this sort of issue in their stride. The DISM commands (and their PowerShell wrappers) in my previous post on Installing Windows Features will run happily if the feature is already installed, and so the output state is well-known regardless of the input state. But the cmdlets to manage IIS don’t – if you create a website with the same name as another website, they’ll fail.

Hence my recommendation that you take simple steps to ensure you’re starting with a clean slate. In IIS terms this means: Make sure that every application you’re developing has its own dedicated website; and get your configuration scripts to delete it as their first step, if it already exists, so they can build it up from scratch.

If (Get-Website | Where-Object { $_.Name -eq “MySite” }) {
   Remove-Website “MySite”
 }

Configuring IIS with PowerShell

At one level, this is very straightforward. Here we create a new application pool and a website to go with it:

Import-Module WebAdministration

$appPool = New-WebAppPool -Name “MyPool”
$webRoot = Join-Path $websiteRoot “MySite”
$website = New-Website -Name "MySite"
                       -PhysicalPath $webRoot
                       -ApplicationPool ($appPool.Name)

This works. (I’m not actually sure under what circumstances you need to import the WebAdministration module – it works fine for me without, but YMMV).

Sadly it gets a little more complicated if you try to do anything more subtle with your website. I never said the PowerShell ecosystem was fully mature. I’ll cover a few interesting points I found when setting up a website of my own.

Setting properties of an application pool with PowerShell

The New-WebAppPool cmdlet returns an application pool object. You can set properties on this, like so:

$appPool.managedRuntimeVersion = “v4.0”
$appPool.managedPipelineMode = “Integrated”

It’s important to be aware that this doesn’t actually save your changes however. The PowerShell objects don’t automatically write themselves to the IIS configuration store – you have to do that manually:

$appPool | Set-Item

Why “Set-Item”? Because in fact, all these IIS components are being treated as just file system entries. Set-Item creates a “file” on “disk” – or in this case, an application pool in IIS. New-WebAppPool does create the application pool, but any subsequent changes will only persist if you pipe them into Set-Item.

As an aside, try typing “dir IIS:” into your PowerShell prompt. Hey presto, the IIS metabase looks like a file system!

Finally on application pools, you might want to set the pool to run as a custom user. Easy:

$credentials = (Get-Credential -Message "Please enter the app pool credentials")
                                     .GetNetworkCredential()
$userName = $credentials.Domain + '\' + $credentials.UserName

$appPool.processModel.identityType = "SpecificUser"
$appPool.processModel.userName = $userName
$appPool.processModel.password = $credentials.Password

In typical Microsoft style, Get-Credential returns an object so highly secure you can’t extract a username and password from it in plain text, and the application pool only takes just such plain text credentials. Fortunately the ultra-secure credential object lets you get a less-secure credential object out of it, and that has the plain text password ready for use. Nice.

If you don’t want the popup, look up ConvertFrom-SecureString or build something based on this StackOverflow post.

Setting properties of a website with PowerShell

This is just the same as dealing with an application pool, right?

Sadly not. As above, PowerShell’s IIS plugins map IIS as a file system, and if you make changes to a website (say, the one returned by New-Website above) they won’t be persisted to disk immediately. But for reasons not fully clear to me, you can’t call Set-Item on a website – maybe it’s just too complex a data structure to overwrite reliably with what you’ve got in memory?

Anyway, the net result is that you need to steer clear of setting properties on the website object. Instead, use the standard Set-ItemProperty cmdlet (used for setting properties on files), and use the IIS “file system” to set the properties you need. For instance:

Set-ItemProperty IIS:\sites\MySite -Name enabledProtocols -Value "http,net.pipe"

New-ItemProperty IIS:\Sites\MySite -Name bindings
                                   -Value @{protocol="net.pipe";bindingInformation="*"}

Do keep an eye out for handy cmdlets (there’s a full list on Microsoft TechNet) that simplify life for you. For example to enable HTTPS you can’t use the above syntax, but can use the following:

New-WebBinding -Name MySite -IP "*" -Port 443 -Protocol https

I will leave it as an exercise to the reader to work out whether I could have done the HTTP and Net.Pipe bindings using equivalent syntax…

Next time I will take a look at using PowerShell with Windows Scheduled Tasks.

Installing Windows Features with PowerShell


22 September 2014, by

Previous article in series

I’m a big fan of automating things. A good candidate is provisioning development machines. Everyone who works on your codebase will have to set it up just so – why not make their lives easier by keeping them away from the tedium of the “Turn Windows features on or off” dialog?

Turn Windows Features On Or Off

If you have Windows 8 / Windows Server 2012, which come with PowerShell 3, this is pretty trivial:

Enable-WindowsOptionalFeature -FeatureName IIS-NetFxExtensibility -Online -All

“Online” means that you are modifying your live Windows installation – as opposed to playing around with an offline Windows install image. “All” means enable all dependent features. And the feature name is… Well, it’s obvious if you know it.

Finding the mapping between features in the dialog box and feature names for the PowerShell script is a bit round the houses as I’ve not been able to find a definitive reference. The best approach I’ve found is this:

Get-WindowsOptionalFeature -Online
     | Where-Object { $_.FeatureName -match "Net" }

Play around with some strings you suspect will appear in the name – thus far I’ve always been able to guess which is the right feature after a bit of digging!

Of course, there’s a whole array of additional cmdlets to play around with Windows configuration (mostly focussed on building install images) – see http://technet.microsoft.com/en-us/library/hh852126.aspx.

What if I’m not on Windows Server 2012?

Fortunately, all is not lost. The general theme I’ve found is that PowerShell cmdlets don’t normally add new functionality that wasn’t available previously – they just make it much more conveniently accessible.

In the case of installing optional features, Windows 7 / Windows Server 2008 include the dism.exe tool which will do the same thing.

dism /Enable-Feature /FeatureName:IIS-NetFxExtensibility /Online

dism /Get-Feature /Online | Where-Object { $_ -match “Net” }

Note that I’m still using PowerShell to run these commands (see the Where-Object filter). I would argue strongly in favour of using PowerShell even if you’re on a slightly older version of Windows – it’s good practice, and will make it easier to upgrade your scripts in future. Yes you could run dism from Cygwin Bash (or even DOS Batch…), but why not take advantage of the opportunity to learn a little PowerShell?

To add extra encouragement, here’s a quick PowerShell function I knocked up to make it easier to run dism without getting all the extra-verbose output it normally spews out, but still capturing the logs in case of error. It’s not quite as neat as the PowerShell 3 built-in version, but it might help tide you over.

function InstallWindowsFeature($featureName) {
Write-Host "Installing $featureName..."

$tempFile = [io.path]::GetTempFileName()
dism /Online /Enable-Feature /FeatureName:$featureName > $tempFile

if ($LastExitCode -ne 0) {
cat $tempFile
Remove-Item $tempFile
Write-Error "Error installing $featureName - provisioning was not completed"
Exit(1)
}

Remove-Item $tempFile
}

InstallWindowsFeature "IIS-WebServerRole"
InstallWindowsFeature "IIS-ManagementScriptingTools"

Next time I’ll take a look at automating IIS configuration using PowerShell.

You Need PowerShell


7 September 2014, by

You need to know PowerShell.

Having got your attention, I will refine my assertion somewhat: If you’re a software engineer and you use Windows, you need to know PowerShell. Why? Partly, because it’s a great scripting language which is extremely powerful and very usable once you get to know it. But you could well argue the same applies to Cygwin Bash. So mostly, because Microsoft say so and it’s easiest to roll with the punches!

Evidence for PowerShell’s increasing importance is all around. For a start, Microsoft are slowly moving forward with Windows Server Core, a GUI-less server OS. The Exchange Server GUI turns everything into PowerShell commands under the covers (and some operations are nigh on impossible without going direct to a PowerShell console). And perhaps the acid test – my searches for “how do I automate X in Windows” give more StackOverflow posts with PowerShell solutions every time I look.

You may have your own preferred scripting language that’s not PowerShell. Provided it’s not DOS Batch this is fine by me. But PowerShell is so useful for automating tasks in Windows, you’re missing a trick if it’s not in your arsenal.

In this series of three posts I’m going to cover a number of useful “how do I automate…” tasks I’ve worked through recently. Because PowerShell is still an emerging technology, and because otherwise PowerShell would make the answers too easy, I’ll pay particular attention to the question “but what if I’ve only got PowerShell version X / Windows Server version 7?”. Hopefully this will help inspire you to start using a bit of PowerShell magic to make your life easier.

Watch this space for the posts…

Recovering data from a corrupted Excel spreadsheet using PowerShell


27 March 2012, by

It’s the last thing anyone wants to see at 5pm after spending the day updating an important spreadsheet:

Excel found unreadable content

I’ve had this once or twice before and Excel has always managed to recover my data perfectly. But on this occasion, Excel simply crashed half way through the recovery process – and continued to do so on all subsequent attempts.

My first thought was of course to look to my backups, but I had nothing more recent than from the previous night – not much comfort after the best part of a day’s effort. Even a Google for “Excel file recovery” didn’t do much good – I downloaded a couple of free trials, but none gave satisfactory results. One did claim to have successfully recovered my data, but did so suspiciously quickly and was unable to display a preview of what it had done. I didn’t trust it sufficiently to click the “Buy Now” button. Help!

(more…)