Thursday, February 28, 2008

Controlling Diskpart from PowerShell

The other day, I got a question from one who had attended my PowerShell training classes: How do I control diskpart from a scripting language? Well, PowerShell can do it, but only because it is layered on top of .Net. And this is what I really like about PowerShell; do you do easily grow out of it.

 

The script below starts diskpart, redirects stdin/stdout and writes/reads to them. The tricky part is starting the process correctly and reading from the subprocess without stalling. The StandardOutput stream does not support timed reads, so if you read one time too many, the process freezes.

 

# ProcessStartInfo - to control stdin/stdout
$si=new-object diagnostics.processstartinfo
$si.filename="$env:windir\system32\diskpart.exe"
$si.RedirectStandardInput=$true
$si.RedirectStandardOutput=$true
$Si.UseShellExecute=$false

# Start process
$p=[diagnostics.process]::start($si)


# The difficult part - reading without stalling - and knowing when to stop
function show($maxwait=60){
# Buffer for matching the prompt
$read=""
$start=Get-Date
do {
# Touch EndOfStream to let peek see next bytes
# Do not know why, but it works
$p.StandardOutput.EndOfStream > $null
# If there is a byte pending
while ($p.StandardOutput.peek() -ne -1) {
# Get the byte as a char
$char=[char]$p.StandardOutput.read()
# Write it to the host
write-host -nonewline $char
# And save it in a buffer
$read+=$char
}
# Exit when maxwait has exceeded or the prompt at the end of the buffer
} until ($read -match "DISKPART\>\s+$" -or ((Get-Date)-$Start).TotalSeconds -gt $maxwait)
write-host
}

do {
show
$command=read-host "Command"
$p.StandardInput.Writeline($command)
} until($command -eq "exit")
# Get remaining output, no prompt at exit, so just timeout
show -maxwait 3

 


The method can also be used with other similar tools, just remember that if you do not have to keep any context, it is much easier to call the tool multiple times with different arguments. In PowerShell you can always capture the output using a simple variable -


sc $env:temp\x.x "list disk" -encoding ascii
$output=diskpart /s $env:temp\x.x

or


$ipconfig=ipconfig

 


Have fun!

2008 Scripting Games, Solution 6

# Advanced Event 6: Prime Time
# http://www.microsoft.com/technet/scriptcenter/funzone/games/games08/aevent6.mspx

# By default stop at 200
param($max=200)
2 # by definition
# Main loop, step by two as only odd numbers can be primes
for($i=3;$i -le $max; $i+=2) {
# Init prime boolean
$prime=$true
# Walk through divisors up to the number at hand
for($d=3; $d -lt $i; $d+=2) {
write-verbose "$i $d $($i % $d)"
if (!($i % $d)) {
# Divisor found, break for and look at next value
$prime=$false
break
}
}
#
if ($prime) {$i}
}

2008 Scripting Games, Solution 5

#Advanced Event 5: You Call That a Strong Password?
#http://www.microsoft.com/technet/scriptcenter/funzone/games/games08/aevent5.mspx

param($password)

# Start score
$score=13
# Load wordlist
$wordlist=type c:\scripts\wordlist.txt

# Tests
$len=$password.length

if ($wordlist -contains $password) {"Is a word";$score--}
if ($wordlist -contains $password.substring(0,($len-1))) {"When the last character is removed, it is a word";$score--}
if ($wordlist -contains $password.substring(1)) {"When the first character is removed, it is a word";$score--}
if ($password[0..$len] -contains "0" -and $wordlist -contains ($password -replace "0","o")) {"Is a word, where the letter 'o' is replaced with zero";$score--}
if ($password[0..$len] -contains "1" -and $wordlist -contains ($password -replace "1","l")) {"Is a word, where the letter 'l' is replaced with digit '1'";$score--}
if (!($len -ge 10 -and $len -le 20)) {"Is shorter than 10 or longer than 20";$score--}
# Find regex character classes at http://msdn2.microsoft.com/en-us/library/20bw873z.aspx
# Class d - digit
if ($password -notmatch "\d") {"Does not contain a digit";$score--}
# Class Ll - Letter, Lowercase
if ($password -cnotmatch "\p{Ll}") {"Does not contain a lowercase letter";$score--}
# Class Lu - Letter, Uppercase
if ($password -cnotmatch "\p{Lu}") {"Does not contain an uppercase letter";$score--}
# Class S - symbol, P - punctuation
if ($password -cnotmatch "\p{S}|\p{P}") {"Does not contain any symbols or punctuation";$score--}
if ($password -cmatch "\p{Ll}{4}") {"Contain more than 4 consecutive lowercase letters";$score--}
if ($password -cmatch "\p{Lu}{4}") {"Contain more than 4 consecutive uppercase letters";$score--}
if (($password[0..$len] | % {$c=0} {$c++; $password[$c..$len] -ccontains $_}) -contains $true) {
"A duplicate letter (case-insensitive) is found"
$score--
}

""
if ($score -le 6) {
"A password score of {0} indicates a weak password" -f $score
}
elseif ($score -le 10) {
"A password score of {0} indicates a moderately-strong password" -f $score
}
else {
"A password score of {0} indicates a strong password" -f $score
}

Sunday, February 24, 2008

2008 Scripting Games, Solution 4

# Advanced Event 4: Image is Everything
# http://www.microsoft.com/technet/scriptcenter/funzone/games/games08/aevent4.mspx

# Being non-native English speaking, I wanted to turn this
# into a generic calendar, that could be used anywhere
# Consequently, I created a function that by default uses
# the default culture and then specified en-US when calling
# it to comply with event rules

function Show-Calendar {
param($startDate=(Get-Date),$Culture)

if ($Culture) {
# Use supplied culture
$culture=[globalization.cultureinfo] $Culture
}
else {
# Use default culture
$culture=Get-Culture
}

# Get first day of week, the calender must be show correct
# e.g. in US Sunday is the first day of week, in Denmark Monday is the first
$firstDayOfWeek=$culture.DateTimeFormat.FirstDayofWeek
# Get the weekdays
$weekdays=$culture.DateTimeFormat.ShortestDayNames
# Calculate the width or the weekdays to right-adjust values
$width=($weekdays | measure-object -maximum length).maximum

# If first day of week is not 0, shift the order
if ([int]$FirstDayOfWeek) {
$ShiftedWeekdays=$weekdays[$FirstDayOfWeek..($WeekDays.count)] + $weekdays[0..($FirstDayOfWeek-1)]
}
else {
$ShiftedWeekdays=$weekdays
}

$StartDate=[datetime]$startDate
# Make sure we start the first day in the month
$StartDate=New-Object datetime $StartDate.year,$StartDate.month,1

# Working date
$Date=$StartDate

# Header
$Date.ToString($culture.DateTimeFormat.YearMonthPattern,$culture)

# Process scriptblock and pipe output to Format-Table
&{
while ($date.Month -eq $StartDate.Month) {
# Week-break code
if ([int]$date.DayOfWeek -eq $firstDayOfWeek -and $week) {
$week
$week=$null
}
# Create week object with days as properties
if (!$week) {
$week=New-Object Object
$ShiftedWeekdays | % {
Add-Member -inputObject $week NoteProperty $_ $null
}
}
# Get the day e.g. the property name
$dayOfWeek=$Weekdays[$date.DayOfWeek]
# Assign value to property
$week.$dayOfWeek="{0,$width}" -f $date.day # Right adjust values
# Values must be right-aligned as the days will $null-values will
# set off Format-Table left-aligning those columns

# Next day
$date=$date.AddDays(1)
}
if ($week) {
# Print out final week, if any
$week
}
} | Format-Table -autosize # Pick up the alias properties and format it

}

# Prompt user
$month=read-host "Enter month in month/year format"
# Split into month and year
$month,$year=$month.split("/")
# And call function with en-us culture
Show-Calendar (New-Object Datetime $year,$month,1) "en-us"

2008 Scripting Games, Solution 3

# Advanced Event 3: Instant (Runoff) Winner
# http://www.microsoft.com/technet/scriptcenter/funzone/games/games08/aevent3.mspx

# Load all votes from the file
$allvotes=type c:\scripts\votes.txt

# While-true in lack of a real do-loop construct
while ($true) {
# Pick up the first name from each line
$votes=$allvotes | ? {$_} | % {$vote,$null=$_.split(","); $vote }
# Save the count
$count=$votes.count
write-verbose "count=$count"
# Use Group combined with select and a calculated property to get percentages
$result=$votes | Group | Select Name,@{Name="Percentage";Expression={$_.count / $count * 100}}
# Do we have a winner? If a percentage is larger than 50, we do
if (($result | measure-object -Maximum Percentage).Maximum -gt 50) {
# Get winner, only one can be returned
$winner=$result | ? {$_.Percentage -gt 50}
# Format output as requested
"The winner is $($winner.name) with $($winner.percentage.tostring('##.#'))% of the vote."
# Exit while
break
}
write-verbose "Winner not found"
# Find candidate that needs to be eliminated
$fewestVotes=$result | sort Percentage | Select -First 1
write-verbose "Fewest votes $fewestVotes"
# Modify votes by removing candidate with fewest votes
# Each line is turned into an array, the array is converted back into
# a comma-separated line excluding the candidate
# Note how -ne can be used to remove values from a collection/array
$allvotes=$allvotes | % {
$votes=$_.split(",")
[string]::join(",",$votes -ne $fewestVotes.Name)
}
}

Wednesday, February 20, 2008

2008 Scripting Games, Solution 2

# Advanced Event 2: Skating on Thin Ice
# http://www.microsoft.com/technet/scriptcenter/funzone/games/games08/aevent2.mspx

# Construct a hash table for skater,score combinations
$skaters=@{}
type c:\scripts\skaters.txt | % {
# Split line into name and an array of results
$name,$results=$_.split(",")
# Calculate the sum, max and min
$measure=$results | measure-object -sum -max -min
# Get the result that counts by excluding max and min scores
$result=$measure.sum - $measure.maximum - $measure.minimum
# Calc the average score
$score=$result / ($results.count-2)
# Save in assoc array
$skaters.$name=$score
}
# Get the 3 highest values, GetEnumerator must be used to get name-value properties
$skaters.GetEnumerator() | sort -desc value | Select -first 3 | % {
# Metal values
$metal="Gold","Silver","Bronze"
# Index into metal
$c=-1
} {
# Increament metal index
$c++
# Write-out winner
"{0} medal: {1}, {2}" -f $metal[$c],$_.name,$_.value
}

2008 Scripting Games, Solution 1

Here's my solution -

# Advanced Event 1: Could I Get Your Phone Number?
# http://www.microsoft.com/technet/scriptcenter/funzone/games/games08/aevent1.mspx

$number=read-host "Enter phone number"
# Combine a pipeline with the foreach statement
# This is necessary as only the first match is needed and break is then used
# to abort the loop
# The pipeline picks words 7 characters long
foreach($word in type c:\scripts\wordlist.txt | ? { $_.length -eq 7 }) {
# Convert letters to numbers
if (($word -replace "[abc]","2" -replace "[def]","3" -replace "[ghi]","4" `
-replace "[jkl]","5" -replace "[mno]","6" -replace "[prs]","7" `
-replace "[tuv]","8" -replace "[wxy]","9") -eq $number ) {
# Emit result in uppercase
$word.toupper()
# Exit foreach loop
break
}
}

Tuesday, February 19, 2008

Using Live ID (a.k.a Passport) on your own site - for free!

Suddenly, it seems like you can do that now. I checked it out when Passport first came out and at that time you had to pay a lot to use the service. Now it seems to be free! I wonder whether you can integrate it directly into IISv6 - could be nice for customer access to our site and I could also see it used in combination with hosted Sharepoint (that I'm running for a small club - no more messing with passwords and email).

Benefits of Web Authentication

The benefits of incorporating Windows Live ID into your Web site include:

  • The ability to use Windows Live gadgets and controls to incorporate authenticated Windows Live services and data into your site.
  • An HTTP-based, platform-neutral interface for implementing Windows Live ID authentication in your existing site, even if it is hosted by a third-party.
  • Freedom from the technical details of authentication! The Windows Live ID authentication service handles this for you.
  • A huge user base: any of the millions of users who have a Windows Live ID can become a user of your site.

Read more in the SDK and get it here.

As soon as Live Id starts to support CardSpace (formerly Infocards) (seems like it is in progress), things are both easy for as well the administrator as the user.

Want to Crash PowerShell?

Try -

$x=1; $x.$y=1

 


It seems like any indirect property reference, when the referencing variable is $null, will crash PowerShell. Consequently, you can do it on any built-in variable like this -


$input.$null=1

 


I will report it to Microsoft. I do not know whether it has been reported or not as my connect.microsoft.com connection to PowerShell does not work :(

Monday, February 18, 2008

2008 (Winter) Scripting Games

Microsoft Script Center has started their yearly Scripting Games February 15 - March 3, 2008.

2008 Winter Scripting Games

If you are new to scripting, need a brush-up or are breathing scripts, I encourage you to join.

I will attended the Advanced Division in PowerShell myself. Why do it in VBScript, when you can do it much easier with PowerShell? ;) And Perl I do not know.

I'm planning to publish my solutions as the deadlines have passed.

Have fun

Thursday, February 14, 2008

Get Active Directory object GUID one-liner

[guid]((([directoryservices.directorysearcher] "(samaccountname=theuser)").findall())[0].properties.getenumerator() | ? { $_.name -eq "objectguid"}).value[0]




Comments -




  • Construct a DirectorySearcher with an LDAP search filter


  • Surround expression with parenthesis to use the returned value (this technique is used multiple times)


  • Find all objects


  • Take the first (and only) by indexing with [0]


  • Get the properties


  • Convert to an enumerator, so they are available in name-value pairs


  • Filter out anything but objectGuid using Where-Object (?)


  • Get the first value, as the value is always a collection


  • Convert the value - it is a byte[] to a guid, by type-casting it with [guid]

Wednesday, February 13, 2008

Code Snippet plugin for Windows Live Writer

Update: It actually tested this layout on my test blog, but formatting is definitely not the same: On msgoodies, only the example without alternating and without container is pretty. I will look into this and do another update.

Thank you Leo Vildosola! You just made me a very happy blogger :)

I found his plugin for Windows Live Writer and it works great with PowerShell scripts (as well as Blogger). Inserting well-formatted, color-coded PowerShell code is now as easy as selecting 'Insert Code Snippet', pasting the code and pressing ok (and can even be easier, see the end of this entry). There are only two drawbacks -

  1. It is not copy-friendly (see below), as there are no line breaks. But my current method does not have that either
  2. The formatting is called MSH and not PowerShell, but I think I can live with that ;)

If you choose a layout with either line numbers or alternate formatting, it becomes copy-friendly.

Some examples

With line numbers and a container (not copy-friendly)

   1: # Set-SMSCacheSize


   2: param([int]$newSizeInMB=2000)


   3:  


   4: $sms=new-object -com UIResource.UIResourceMgr


   5: $ci=$sms.GetCacheInfo()


   6: if ($ci.TotalSize -ne $newSizeInMB) {


   7:     $ci.TotalSize=$newSizeInMB


   8:     "Size set to $newSizeInMB"


   9: }


  10: else {


  11:     "Size $newSizeInMB already correct"


  12: }






Alternating lines, no container, no line numbers (copy-friendly)





# Set-SMSCacheSize


param([int]$newSizeInMB=2000)


 


$sms=new-object -com UIResource.UIResourceMgr


$ci=$sms.GetCacheInfo()


if ($ci.TotalSize -ne $newSizeInMB) {


    $ci.TotalSize=$newSizeInMB


    "Size set to $newSizeInMB"


}


else {


    "Size $newSizeInMB already correct"


}







No line numbers, no container (not copy-friendly)




# Set-SMSCacheSize
param([int]$newSizeInMB=2000)

$sms=new-object -com UIResource.UIResourceMgr
$ci=$sms.GetCacheInfo()
if ($ci.TotalSize -ne $newSizeInMB) {
$ci.TotalSize=$newSizeInMB
"Size set to $newSizeInMB"
}
else {
"Size $newSizeInMB already correct"
}






No line numbers, no container, alternating lines (copy-friendly)





# Set-SMSCacheSize


param([int]$newSizeInMB=2000)


 


$sms=new-object -com UIResource.UIResourceMgr


$ci=$sms.GetCacheInfo()


if ($ci.TotalSize -ne $newSizeInMB) {


    $ci.TotalSize=$newSizeInMB


    "Size set to $newSizeInMB"


}


else {


    "Size $newSizeInMB already correct"


}







No line numbers, container, alternating lines (copy-friendly)





# Set-SMSCacheSize


param([int]$newSizeInMB=2000)


 


$sms=new-object -com UIResource.UIResourceMgr


$ci=$sms.GetCacheInfo()


if ($ci.TotalSize -ne $newSizeInMB) {


    $ci.TotalSize=$newSizeInMB


    "Size set to $newSizeInMB"


}


else {


    "Size $newSizeInMB already correct"


}






In the future, I'll probably stick with the no container, alternating lines style, although I would be glad to get rid of the alternation. I do not like how the container behaves when the browser is resized - YMMV.



BTW: It seems like the copy-friendliness is depending upon whether the generated HTML contains a single <pre> or multiple ones. Alternating lines and line-numbers uses a <pre> per line; without, a single <pre> is used.



Silent Mode



The plugin has another smart feature. You can enable silent mode. With silent mode, your clipboard is inserted with the lastest settings, just by clicking the 'Insert Code Snippet'. If you want to disable silent mode and do something else, simply ctrl-click the link. This is a great productivity feature.

Exchange/OWA/Outlook/WDS search

This will give you the overview in a few seconds - worth a read.

Shame it did not include Sharepoint, but I think a good starting point is here.

WinSxS and Format-Sddl

While cleaning up on my harddisk - how come that I always run out of space not matter how big it is? - I came across the 5 GB in \Windows\winsxs. So could I get rid of these? Surfing the net, I found this article, and no, this seems to be yet another internal Windows folder that you just have to live with. Well the article showed the security of the folder in SDDL format and I liked that format, so why not create a PowerShell script, that could format an SDDL string in the same, readable way? And while I was at it: Why not translate SIDs to account names? As thought, then done -

# Format SDDL strings in a more readable format
# If -TranslateSid is present, Sids will be translated to their account equivalent
param([switch]$translateSid)
process {
$sddl=$_
if ($sddl -is [string]) {
# Simply use the value
}
else {
# If input is any thing else, pick up SDDL property
# This makes it possible to pipe in the output from Get-Acl or Select
$sddl=$_.sddl
}
# Insert linefeed before G: D: or S: blocks
# Insert linefeed and indent values in ()
$sddl=$sddl -replace "([GDS]):","`n`$1:" -replace "(\([^\)]+\))","`n `$1"
if ($translateSid.isPresent) {
# Match all SIDs and translate them to NTAccount format
[regex]::Matches($sddl,"(S(-\d+){2,8})") | sort index -desc | % {
# Save value in case translatation fails
$name=$_.value
$sid=[system.security.principal.securityidentifier] $name
# Remove matched value
$sddl=$sddl.remove($_.index,$_.length)
# Translate, suppress non-translatable exception
trap [System.Management.Automation.MethodInvocationException] {continue} `
$name=$sid.Translate([system.security.principal.ntaccount])
# Insert translated name
$sddl=$sddl.insert($_.index,$name)
}
}
$sddl
}


 



The script shows a couple of techniques -




  • Testing the type of pipeline input and using it accordingly


  • Use of regular expressions for replacement and picking up values from the matched values ($1)


  • Use of the regex object to walk-through the matched values. This is necessary in cases where the new value needs to be calculated.


  • How to translate a SID string to an account name using the .Net classes in system.security.principal namespace


  • How to suppress a specific exception using a trap statement



 



Example of how to use it -



image



Have fun!

Monday, February 11, 2008

x64 Clients on Hyper-V

This is almost too easy. Just create a client - you do not have to specify 32 or 64 bit anywhere. Select a 64-bit installation image and on you go.

Here's a screenshot, the white PowerShell window is from the host, the rest from the virtual server.

image

 

Nice!

Wednesday, February 06, 2008

Importing Virtual Server guests into Hyper-V

As said, I was going to figure out why the import failed. I started off looking at the permissions of C:\ProgramData\Microsoft\Windows\Hyper-V. It turns out that a security identifier called Virtual Machines has full control. Naturally, I jumped to my c:\guest\psk10 folder at tried to add the security identifier. But the dialog box cannot find it and as it is not a local group on my server, it must be a new built-in security identifier - one that the brand-new WS08 GUI does not recognize!

Luckily, PowerShell exists, so it is quite easy to get rid of all that GUI translation stuff and get to the real deal -

image

I bet S-1-5-83-0 is Virtual Machines and a to confirm it, I went searching on MSDN -

So I have to prove my point myself -

> $acl=get-acl C:\Guests\psk10
> $acl.SetSecurityDescriptorSddlForm( ($acl.sddl + "(A;;FA;;;S-1-5-83-0)(A;OICIIO;0x101f01ff;;;S-1-5-83-0)") )
> set-acl C:\Guests\psk10 -AclObject $acl
>

Voila, its there -

image

Microsoft, get you documentation up-to-date!

After this, I tried the import. Looking in the eventlog, I found "Failed to find virtual machine import files under location 'C:\Guests\psk10\'." I wonder: Have I misunderstood "Import" and is Import simply an operation that adds an existing Hyper-V Virtual Machine? Is there not easy way to migrate Virtual Server guests? Maybe, I have to look at SCVMM.

I tried to find some information about this, but did not succeed. In my search, I found another interesting item - the team blog at http://blogs.technet.com/virtualization/

A first look at Windows Server 2008 and Hyper-V

After downloading the Windows Server 2008 RTM, yesterday, I upgraded my RC1 system to RTM. No problems what so ever - great.

Hardware

My system is a desktop, a Dell Dimension E520 with 4 Gigs or RAM and I am very satisfied with it: It is silent, uses only 130 W when idle and runs 10 Virtual Machines each having 300 GB without problems. If they all apply hotfixes or are running similar IO heavy activity at the same time, you feel the IO bottleneck but besides that, its quite fast.

Installation

Next, I was looking a how to get Hyper-V running, so I could get rid of Virtual Server. Even though I was quite happy with that, you know how it is: You have to try the latest and the greatest. When things starts to run well, it is time to change it...

Well, it turns out that the pre-release of Hyper-V is part of the RTM installation, so I simply added the Hyper-V role to the server and restarted it. After restart it is a matter of finding Administrative Tools and select Hyper-V Manager. You also have to accept a pre-release EULA.

Migration of Virtual Server guests

I started trying to import an existing Virtual Server guest, but got a badly written error message: The operation was passed an invalid parameter. I tried both with and with-out 'Reuse old virtual machine IDs' (whatever that means - haven't found a help topic yet). I also tried to give NETWORK SERVICE modify access to the folder, as I can see the Microsoft Hyper-V Image Management Service uses that account. Did not help either.

Snapshot

Next, I created a new Virtual Machine and liked a lot that I asked me about OS installation in the wizard. I selected a Windows Server 2003 SP2 ISO image and off it went. I started to play with snapshots and took one while the installation had copied 6% of the files. Then I waited until it reached 9% and applied the snapshot - and I was back in time. Very nice.

I also tried the other scenario, where you can create a new snapshot before applying one. Soon, I had a bunch of snapshots and it is very easy to jump back and forth between the versions - but I guess, it is also very easy to get confused and you have to take care if the server is a domain controller or your have some other kind of distributed system.

Mouse Control

A small warning: When you use the new remote control feature - called Virtual Machine Connection - your have to release your mouse using CTRL+SHIFT+LEFT ARROW. Well, I did not succeed in that across Remote Desktop. I had to visit the server (approx 60 cm behind me!) and do it from the console. It is configurable, but CTRL+ALT+SHIFT did not work either. I could only make it work in full screen or if I choose to send "Windows key combinations" to the remote server. For me, this is a bug.

But as I browsed the web trying to find something about the importing problem, I found out that this is actually already documented in the release notes. Quote "The use of Virtual Machine Connection within a Terminal Services session is not supported".

BTW: If you disconnect and re-connect, you can get your mouse activated outside of the Virtual Machine Connection window. If your server is farther away than mine, that may be an option.

Resume after restart of host

Another good thing: With Virtual Server you had to configure an account, to enable a Virtual Machine to start automatically. This is not the case with Hyper-V. Actually, "Automatically start if it was running when the service stopped" is selected by default.

Conclusion so far

Seems ok, but I have to look more closely at the importing problem and the scripting interface.

Tuesday, February 05, 2008

FYI - Visio Stencils for OCS 2007

A set of stencils for OCS depicting the server roles and components can be downloaded from here.

Monday, February 04, 2008

Windows Server 2008 and SP1 RTM'ed

Quick note - Windows Vista SP1 has RTM'ed according to the Vista blog and Windows Server 2008 also RTM'ed check more about this in the press release.

Update - want to know more about SP1 and the availability then check Paul Thurrot's FAQ

Friday, February 01, 2008

Tips for upgrading to Exchange 2007 SP1

FYI - Rui Silva has produced a good checklist to use before upgrading to SP1 called Notes from the field furthermore Andy Grogan from telnet 127.0.0.1 has a tip for installing the management tool on XP, when you have problems with the Remote Registry service.