Skip to main content

Cloud

Converting a DiskPart script to PowerShell on Windows Server 2012

Windows Server 2012 and PowerShell 3.0 offer thousands of new cmdlets to make any scripter happy. One set of cmdlets had me intrigued when I first heard about them. The cmdlets in the storage module interested me because frequently I would have to deploy a bunch of new Exchange servers and depending on the design sometimes I would need to prepare a lot of disks and create mountpoints. Depending on the numbers of servers and disks involved I would either do everything manually or use a diskpart script. I always for automating things like this as much as possible so when I heard about the new storage cmdlets in 2012 I was pleasantly surprised.

For anyone who has used diskpart scripts you’ll know they aren’t always easy to implement and require some testing and tweaking to get them fully automated. Good luck trying to put a lot of logic in a diskpart script too. The only switch I found useful was the NOERR one, which basically told the script to continue and “ignore the man behind the curtain” when an error was encountered. Here’s a snippet of one such script. In it I’m creating two disks, one for a database and one for a log, then linking them to a directory as a mountpoint.

select Disk 1
online disk NOERR
select Disk 1
convert GPT NOERR
attributes disk clear readonly NOERR
create partition primary NOERR
format FS=NTFS LABEL=DB1 UNIT=64K QUICK NOERR
assign mount=E:\Mountpoints\Databases\DB1
select Disk 2
online disk NOERR
select Disk 2
convert GPT NOERR
attributes disk clear readonly NOERR
create partition primary NOERR
format FS=NTFS LABEL=Logs1 UNIT=64K QUICK NOERR
assign mount=E:\Mountpoints\Logs\Logs1

 
I know. It looks a little messy, but it actually works and saved me a ton of time. What you don’t see is how I actually created this script. I used a combination of PowerShell scripting to get the disk information by using output from diskpart, then with some CSV file manipulation in Excel I mapped my disks along with their respective database and log names and paths. Then I used PowerShell once again to import my edited CSV file and spit out this diskpart script shown above. The final part was obviously running diskpart in script mode (i.e. “diskpart /s drives.txt”). I even took my PowerShell script a step further and as I had my script create my diskpart commands to layout the drives as I wanted them, I also had it create an “undo” script which reverted all the disks back to their initial states. This proved to be very helpful in my lab for testing as well as giving me an out when dealing with 30+ LUNs x 16 servers in production.
The downside is that these cmdlets are only available with Windows Server 2012 and Windows 8 so if you’re trying to automate disk management for Windows Server 2008 or other OS, you’ll need to stick with diskpart for now.
So how do we translate diskpart scripts to Windows Server 2012 and PowerShell 3.0?
First, let’s take a look at the new storage commands available in 2012. There’s more storage cmdlets than these but we’ll be focusing on this group of commands for this exercise.
 

Get-Command -Module storage
 
CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Alias           Initialize-Volume                                  storage
Function        Add-InitiatorIdToMaskingSet                        storage
Function        Add-PartitionAccessPath                            storage
Function        Add-PhysicalDisk                                   storage
Function        Add-TargetPortToMaskingSet                         storage
Function        Add-VirtualDiskToMaskingSet                        storage
Function        Clear-Disk                                         storage
Function        Connect-VirtualDisk                                storage
Function        Disable-PhysicalDiskIndication                     storage
Function        Disconnect-VirtualDisk                             storage
Function        Dismount-DiskImage                                 storage
Function        Enable-PhysicalDiskIndication                      storage
Function        Format-Volume                                      storage
Function        Get-Disk                                           storage
Function        Get-DiskImage                                      storage
Function        Get-FileIntegrity                                  storage
Function        Get-InitiatorId                                    storage
Function        Get-InitiatorPort                                  storage
Function        Get-MaskingSet                                     storage
Function        Get-OffloadDataTransferSetting                     storage
Function        Get-Partition                                      storage
Function        Get-PartitionSupportedSize                         storage
Function        Get-PhysicalDisk                                   storage
Function        Get-ResiliencySetting                              storage
Function        Get-StorageJob                                     storage
Function        Get-StoragePool                                    storage
Function        Get-StorageProvider                                storage
Function        Get-StorageReliabilityCounter                      storage
Function        Get-StorageSetting                                 storage
Function        Get-StorageSubSystem                               storage
Function        Get-SupportedClusterSizes                          storage
Function        Get-SupportedFileSystems                           storage
Function        Get-TargetPort                                     storage
Function        Get-TargetPortal                                   storage
Function        Get-VirtualDisk                                    storage
Function        Get-VirtualDiskSupportedSize                       storage
Function        Get-Volume                                         storage
Function        Get-VolumeCorruptionCount                          storage
Function        Get-VolumeScrubPolicy                              storage
Function        Hide-VirtualDisk                                   storage
Function        Initialize-Disk                                    storage
Function        Mount-DiskImage                                    storage
Function        New-MaskingSet                                     storage
Function        New-Partition                                      storage
Function        New-StoragePool                                    storage
Function        New-StorageSubsystemVirtualDisk                    storage
Function        New-VirtualDisk                                    storage
Function        New-VirtualDiskClone                               storage
Function        New-VirtualDiskSnapshot                            storage
Function        Optimize-Volume                                    storage
Function        Remove-InitiatorId                                 storage
Function        Remove-InitiatorIdFromMaskingSet                   storage
Function        Remove-MaskingSet                                  storage
Function        Remove-Partition                                   storage
Function        Remove-PartitionAccessPath                         storage
Function        Remove-PhysicalDisk                                storage
Function        Remove-StoragePool                                 storage
Function        Remove-TargetPortFromMaskingSet                    storage
Function        Remove-VirtualDisk                                 storage
Function        Remove-VirtualDiskFromMaskingSet                   storage
Function        Rename-MaskingSet                                  storage
Function        Repair-FileIntegrity                               storage
Function        Repair-VirtualDisk                                 storage
Function        Repair-Volume                                      storage
Function        Reset-PhysicalDisk                                 storage
Function        Reset-StorageReliabilityCounter                    storage
Function        Resize-Partition                                   storage
Function        Resize-VirtualDisk                                 storage
Function        Set-Disk                                           storage
Function        Set-FileIntegrity                                  storage
Function        Set-InitiatorPort                                  storage
Function        Set-Partition                                      storage
Function        Set-PhysicalDisk                                   storage
Function        Set-ResiliencySetting                              storage
Function        Set-StoragePool                                    storage
Function        Set-StorageSetting                                 storage
Function        Set-StorageSubSystem                               storage
Function        Set-VirtualDisk                                    storage
Function        Set-Volume                                         storage
Function        Set-VolumeScrubPolicy                              storage
Function        Show-VirtualDisk                                   storage
Function        Update-Disk                                        storage
Function        Update-HostStorageCache                            storage
Function        Update-StorageProviderCache                        storage
 
 
(Get-Command -Module storage).count
84
 

Sweeeet! (a la Eric Cartman)

So the scenario I’m going to is one where I have the following set of Exchange servers with these disk requirements:

  • C: – 40GB boot volume
  • D: – CD-ROM
  • E: – 30GB pagefile drive
  • F: – 60GB application drive (Exchange, mountpoint root, other apps)
  • Three databases with associated log volumes:
    • 3 x 20GB disks – our log LUNs
    • 3 x 100GB disks – our database LUNs
    • 1 x 150GB disk – our restore LUN
  • F:\ExchangeData\ – root directory for our mountpoints. Each database and associated log mountpoints will be linked to a subdirectory based on the database name (i.e. F:\ExchangeData\DB1\Database and F:\ExchangeData\DB1\Logs)

Let’s take a look at the disks on my lab machine.

Get-Disk | ft -AutoSize
 
Number Friendly Name                             OperationalStatus Total Size Partition Style
------ -------------                             ----------------- ---------- ---------------
0      VMware, VMware Virtual S SCSI Disk Device Online                 40 GB MBR
7      VMware, VMware Virtual S SCSI Disk Device Online                 60 GB MBR
9      VMware, VMware Virtual S SCSI Disk Device Online                 30 GB MBR
2      VMware, VMware Virtual S SCSI Disk Device Offline               100 GB RAW
6      VMware, VMware Virtual S SCSI Disk Device Offline                20 GB RAW
4      VMware, VMware Virtual S SCSI Disk Device Offline                20 GB RAW
3      VMware, VMware Virtual S SCSI Disk Device Offline               100 GB RAW
8      VMware, VMware Virtual S SCSI Disk Device Offline               150 GB RAW
1      VMware, VMware Virtual S SCSI Disk Device Offline               100 GB RAW
5      VMware, VMware Virtual S SCSI Disk Device Offline                20 GB RAW
 
 
Now, let’s show some other details about the disks. You can easily identify the system disk versus the pagefile and application disks and lastly the Exchange data disks we get to play around with.
 
 
get-disk|ft -AutoSize Number,@{label="Size(MB)";expression={"{0:N0}" -f (($_.size)/1MB)}},@{label="Allocated Size(MB)";expression={"{0:N0}" -f (($_.allocatedsize)/1MB)}},NumberOfPartitions,PartitionStyle,isReadOnly,isSystem,isBoot
 
Number Size(MB) Allocated Size(MB) NumberOfPartitions PartitionStyle isReadOnly isSystem isBoot
------ -------- ------------------ ------------------ -------------- ---------- -------- ------
     0 40,960   40,960                              2 MBR                 False     True   True
     7 61,440   61,439                              1 MBR                 False    False  False
     9 30,720   30,719                              1 MBR                 False    False  False
     2 102,400  0                                   0 RAW                  True    False  False
     6 20,480   0                                   0 RAW                  True    False  False
     4 20,480   0                                   0 RAW                  True    False  False
     3 102,400  0                                   0 RAW                  True    False  False
     8 153,600  0                                   0 RAW                  True    False  False
     1 102,400  0                                   0 RAW                  True    False  False
     5 20,480   0                                   0 RAW                  True    False  False

 
Now, what generally happens is that you would give the server and storage guys these requirements and they would most likely give you a server with the OS already installed along with some basic applications (anti-Virus, desktop management tools, etc) and all of the non-Exchange data disks already configured. For example, they would give you the C:, D:, E: and F: drives and a bunch of RAW disks for you to deal with. So we don’t want to touch the already provisioned disks except for creating some directories for the mountpoints we’ll be using.
So how do we exclude these drives from our script? Well we could filter them out a few ways, by size, disk number, name, etc. If we truly have a standard build for these machines, and we should, then we can probably just use the size as another filter. You need to determine which filter best works in your situation. If you have similar sizes across different sets of disks, then maybe you need to add a couple of filters to narrow it down to just the Exchange data disks.
For example, excluding the system disk would go something like this:
 

$nonsysdisks=get-disk | where {$_.issystem -eq $false -and $_.isboot -eq $false}

 
Now let’s extend that further to excluding the E: (Pagefile) and F: (Applications) drives so that we’re left with only the database, log and restore LUNs. For my environment I’m going to filter by the allocated size. Since it should be ‘0’ for all unprovisioned disks the query looks like this.
 

$exchdatadisks= Get-Disk| where {$_.allocatedsize -eq 0}
 
Number Friendly Name                             OperationalStatus Total Size Partition Style
------ -------------                             ----------------- ---------- ---------------
2      VMware, VMware Virtual S SCSI Disk Device Offline               100 GB RAW
6      VMware, VMware Virtual S SCSI Disk Device Offline                20 GB RAW
4      VMware, VMware Virtual S SCSI Disk Device Offline                20 GB RAW
3      VMware, VMware Virtual S SCSI Disk Device Offline               100 GB RAW
8      VMware, VMware Virtual S SCSI Disk Device Offline               150 GB RAW
1      VMware, VMware Virtual S SCSI Disk Device Offline               100 GB RAW
5      VMware, VMware Virtual S SCSI Disk Device Offline                20 GB RAW

 
Now group them by size to confirm how many of each you have. Hopefully the numbers you end up with match with your Exchange design.
 

$exchdatadisks|group size|ft count,@{label="Size(GB)";expression={$_.name/1GB}} –AutoSize
 
Count Size(GB)
----- --------
    3      100
    3       20
    1      150

 
Assuming everything checks out we can now move onto provisioning our Exchange data disks. I suggest working with the disks according to their sizes and intended function. This will make it easier to loop through the groups of disks and perform operations on them.
If you recall our directory structure is going to look like this:

 
 
Now let’s create the folder structure we need.

 
 
 
# Create root level directory for ExchangeData and sub folder for Restores
New-Item -ItemType directory -Path F:\ExchangeData # Creates the root-level folder
New-Item -ItemType directory -Path F:\ExchangeData\Restore # Creates the sub-level for the Restore volume
 
# Create Database and Log directories
1..3|%{
 
New-Item -ItemType directory -Path F:\ExchangeData\DB$_ # Creates the first sub-level DBx directories (i.e. DB1, DB2, DB3)
New-Item -ItemType directory -Path F:\ExchangeData\DB$_\Database # Creates the second sub-level dirs for the databases (mountpoint dirs)
New-Item -ItemType directory -Path F:\ExchangeData\DB$_\Logs # Creates the second sub-level dirs for the Logs (mountpoint dirs)
 
}

 
Verify the structure in Windows Explorer.
 
image
 
Here’s the completed script that ties everything together, creating the directory structure plus preparing the disks and mountpoints. You would just run this on each server. You’ll notice that I put a delay in the script. After running this multiple times I noticed occasionally I would get some strange “read-only” errors when creating the partition and formatting the volume so I gave it a few seconds delay before formatting the volume and that seemed to do the trick.
 

# Create root level directory for ExchangeData and sub folder for Restores
New-Item -ItemType directory -Path F:\ExchangeData # Creates the root-level folder
New-Item -ItemType directory -Path F:\ExchangeData\Restore # Creates the sub-level for the Restore volume
 
# Create Database and Log directories
1..3|%{
 
New-Item -ItemType directory -Path F:\ExchangeData\DB$_ # Creates the first sub-level DBx directories (i.e. DB1, DB2, DB3)
New-Item -ItemType directory -Path F:\ExchangeData\DB$_\Database # Creates the second sub-level dirs for the databases (mountpoint dirs)
New-Item -ItemType directory -Path F:\ExchangeData\DB$_\Logs # Creates the second sub-level dirs for the Logs (mountpoint dirs)
 
}
 
# Gather DB, Log and Restore disks
$exchdbdisks= Get-Disk| where {$_.allocatedsize -eq 0 -and $_.size -eq 107374182400}
$exchlogdisks= Get-Disk| where {$_.allocatedsize -eq 0 -and $_.size -eq 21474836480}
$restoredisk=Get-Disk| where {$_.size -eq 161061273600}
 
# Initialize and format restore volume
Initialize-Disk $restoredisk.number -PartitionStyle GPT
$newpartition=New-Partition -DiskNumber $restoredisk.number -UseMaximumSize
Start-Sleep -Seconds 3
$newpartition|Format-Volume -FileSystem NTFS -NewFileSystemLabel Restore -AllocationUnitSize 65536 -Confirm:$false
# "Mount up"
Add-PartitionAccessPath -DiskNumber $restoredisk.number -PartitionNumber 2 -AccessPath "F:\ExchangeData\Restore\"
 
1..3|ForEach {
    # Initialize and format database volumes
    Initialize-Disk -Number $exchdbdisks[($_-1)].Number -PartitionStyle GPT
    $newpartition=New-Partition -DiskNumber $exchdbdisks[($_-1)].Number -UseMaximumSize
    Start-Sleep -Seconds 3
    $newpartition|Format-Volume -FileSystem NTFS -NewFileSystemLabel DB$_ -AllocationUnitSize 65536 -Confirm:$false
    # "Mount up"
    Add-PartitionAccessPath -DiskNumber $exchdbdisks[($_-1)].Number -PartitionNumber 2 -AccessPath "F:\ExchangeData\DB$_\Database\"
 
    # Initialize and format log volumes
    Initialize-Disk -Number $exchlogdisks[($_-1)].Number -PartitionStyle GPT
    $newpartition=New-Partition -DiskNumber $exchlogdisks[($_-1)].Number -UseMaximumSize
    Start-Sleep -Seconds 3
    $newpartition|Format-Volume -FileSystem NTFS -NewFileSystemLabel Logs$_ -AllocationUnitSize 65536 -Confirm:$false
    # "Mount up"
    Add-PartitionAccessPath -DiskNumber $exchlogdisks[($_-1)].Number -PartitionNumber 2 -AccessPath "F:\ExchangeData\DB$_\Logs\"
}

 
Here’s a quick summary showing the mountpoints we created:
 

SystemName Name                                              Size(GB) FreeSpace(GB) % Free Label
---------- ----                                              -------- ------------- ------ -----
WIN2K12    C:\                                               39.7     27.4          69 %
WIN2K12    F:\                                               59.9     59.8          100 %  Applications
WIN2K12    F:\ExchangeData\DB1\Database\                     99.9     99.8          100 %  DB1
WIN2K12    F:\ExchangeData\DB2\Database\                     99.9     99.8          100 %  DB2
WIN2K12    F:\ExchangeData\DB3\Database\                     99.9     99.8          100 %  DB3
WIN2K12    F:\ExchangeData\DB1\Logs\                         19.9     19.8          100 %  Logs1
WIN2K12    F:\ExchangeData\DB2\Logs\                         19.9     19.8          100 %  Logs2
WIN2K12    F:\ExchangeData\DB3\Logs\                         19.9     19.8          100 %  Logs3
WIN2K12    E:\                                               29.9     29.8          100 %  Pagefile
WIN2K12    F:\ExchangeData\Restore\                          149.9    149.8         100 %  Restore
WIN2K12    \\?\Volume{1e1d049b-00ea-11e2-93e8-806e6f6e6963}\ 0.3      0.1           31 %   System Reserved

 
The mini script used to produce this report:
 

####################################################################
# Begin function for checking mountpoints and free space
function getDiskFreeSpace
 {
Get-WmiObject Win32_Volume -Namespace root/cimv2 -computername $args -Filter "DriveType='3'" | `
Select-Object SystemName, Name, @{Name="Size(GB)";Expression={"{0:N1}" -f($_.Capacity/1gb)}},@{Name="FreeSpace(GB)";Expression={"{0:N1}" -f($_.freespace/1gb)}},@{Name="% Free";Expression={"{0:P0}" -f($_.freespace/$_.capacity)}},Label | `
 Sort-Object -property "Label"  | `
 Format-Table -AutoSize
 }
# End function for checking mountpoints and free space
####################################################################
getDiskFreeSpace localhost|out-default

 
When scripting against disks you might want to use filters like this to avoid destroying disks you didn’t intend to touch. And that would be bad (a la Mr. Mackey).
I hope this post inspires you develop your own automated storage scripts.
Until next time.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.