cOnlineAllDisks.psm1

enum PartitionType
{
    GPT
    MBR
}

<#
   This resource ensures all disks are online, read/write, partitioned and formatted
   [DscResource()] indicates the class is a DSC resource
#>

[DscResource()]
class cOnlineAllDisks
{
    <#
        This property is the name of the system - its not used for anything
        other than they key.
    #>

    [DscProperty(Key)]
    [string]$Name

    [DscProperty()]
    [PartitionType]$PartitionType = [PartitionType]::MBR 

    <#
        This is a JSON representation of an array of drives using a PSCustomObject
        Object format is:
            DriveLetter DriveSize AllocUnit
            ----------- --------- ---------
            D 512 64
            Q 1024
            R 512
        Typically, a new DriveArray is created via:
            $VMDiskArray = "D:512@64,Q:1024,R:512"
 
            $DiskArray = @()
            $VMDiskArray.Split(",") | ForEach-Object {
                $driveLetter = $_.split(':')[0]
                $DriveSize = $_.split(':')[1].Split('@')[0]
                $AllocUnit = $_.split(':')[1].Split('@')[1]
                $DiskObject = [PSCustomObject]@{
                    DriveLetter = $DriveLetter
                    DriveSize = $DriveSize
                    AllocUnit = $AllocUnit
                }
                $DiskArray += $DiskObject
            }
 
        This should be passed via $DiskArray | ConvertTo-Json
    #>

    [DscProperty()]
    [string] $DriveArrayInJSON

    [PSCustomObject[]] $DriveArray

    <# This toggles the volume lable between "Disk1" and "E_Drive" #>
    [DscProperty()]
    [Bool]$UseDriveLetterInVolumeName = $False
    
    <#
        This method is equivalent of the Get-TargetResource script function.
        The implementation should use the keys to find appropriate resources.
        This method returns an instance of this class with the updated key
         properties.
    #>

    [cOnlineAllDisks] Get()
    {
        $this.Name = $Env:COMPUTERNAME              
        #This is the default PartitionType
        $this.PartitionType = [PartitionType]::MBR  
        
        #Convert from Json
        try {
            $this.DriveArray = $This.DriveArrayInJSON | ConvertFrom-Json
        } catch {
            throw "Invalid DriveArrayInJSON"
        }

        return $this
    }
    
    <#
        This method is equivalent of the Test-TargetResource script function.
        It should return True or False, showing whether the resource
        is in a desired state.
    #>

    [bool] Test()
    {
        Write-Verbose "OnlineDisks:Test - Start"
        Write-Verbose "OnlineDisks:Test - Start - UseDriveLetterInVolumeName: $This.UseDriveLetterInVolumeName"
        #Default to PASS
        $TestResult = $true
        try {
            #See if we have any offline disks
            $OfflineDisks = Get-Disk | Where-Object {$_.IsOffline -eq $true}    
            if ($OfflineDisks -ne $null) {
                Write-Verbose "OnlineDisks:Test - Found an offline disk"
                $TestResult = $false
            }

            #See if we have any RAW partitions
            $RawDisks = Get-Disk | Where-Object { $_.partitionstyle -eq 'raw' }
            #Any raw disks means we need to run SET
            if  ($RawDisks -ne $null) {
                Write-Verbose "OnlineDisks:Test - Found a RAW disk"
                $TestResult = $false
            }

            #Now see if any disks have no partitions
            Get-Disk | Sort-Object { $_.Number } | ForEach-Object {
                $Part = Get-Partition -DiskNumber $_.Number  -ErrorAction SilentlyContinue
                if ( $Part -eq $null ) {
                    Write-Verbose "OnlineDisks:Test - Found an unpartitioned disk"
                    $TestResult = $false 
                }
            } 
            #Finally, see if any volumes need to be formatted
            Get-Volume | Where-Object {$_.DriveType -eq "fixed"} | ForEach-Object { 
                if ($_.Size -eq 0) {
                    Write-Verbose "OnlineDisks:Test - Found an unformatted disk"
                    $TestResult = $false
                }
            }
            #All tests run, return results
            return $TestResult 

        } finally {
            Write-Verbose "OnlineDisks:Test - End"
        }
    }

    <#
        This method is equivalent of the Set-TargetResource script function.
        It sets the resource to the desired state.
    #>

    [void] Set()
    {
        Write-Verbose "OnlineDisks:Set - Start"
        try {
            #Convert from Json
            try {
                $this.DriveArray = $This.DriveArrayInJSON | ConvertFrom-Json
            } catch {
                throw "Invalid DriveArrayInJSON"
            }

            #Online all disks
            $OfflineDisks = Get-Disk | Where-Object {$_.IsOffline -eq $true}
            if ($OfflineDisks -ne $null ) {
                $OfflineDisks | ForEach-Object {
                    $DiskNumber = $_.DiskNumber
                    Write-Verbose "OnlineDisks:Set - Onlining disk $DiskNumber"
                    Set-Disk -InputObject $_ -IsOffline $false 
                    Write-Verbose "OnlineDisks:Set - Setting disk $DiskNumber to Read/Write"
                    Set-Disk -InputObject $_ -IsReadOnly $false
                }
            } else {
                Write-Verbose "OnlineDisks:Set - All disks online"
            }


            #Initializing Raw disks
            if (-not $this.PartitionType) {$this.PartitionType = [PartitionType]::MBR}
            $RawDisks = Get-Disk | Where-Object { $_.partitionstyle -eq 'raw' } | Sort-Object {$_.Number }
            if ($RawDisks -ne $Null) { 
                $RawDisks | ForEach-Object {
                    $DiskNumber = $_.Number 
                    Write-Verbose ("OnlineDisks:Set - Initializing RAW Disk $DiskNumber as " + ($this.PartitionType.ToString()))
                    Initialize-Disk -Number $DiskNumber -PartitionStyle ($this.PartitionType.ToString()) 
                }
            } else {
                Write-Verbose "OnlineDisks:Set - All disks initialized"
            }

            #Partition disks with no partitions
            Get-Disk | Sort-Object {$_.Number} | ForEach-Object {
                $DiskNumber = $_.Number 
                $Part = Get-Partition -DiskNumber $DiskNumber  -ErrorAction SilentlyContinue
                if ($Part -eq $null) {
                    $NextDrive = $this.GetNextDisk() 
                    if ($NextDrive -ne $null) {
                        Write-Verbose ("OnlineDisks:Set - Partitioning Disk $DiskNumber as " + $NextDrive.DriveLetter)
                        New-Partition -DiskNumber $DiskNumber -DriveLetter $NextDrive.DriveLetter -UseMaximumSize
                    } else {
                        throw "No more drive letters"
                    }
                }
            }

            #Format partitions that are not formatted
            Get-Volume | Where-Object {$_.DriveType -eq "fixed"} | Where-Object {$_.Size -eq 0} |
                ForEach-Object {
                    $CurDriveLetter = $_.DriveLetter 
                    $curDrive = $This.DriveArray.Where({$_.Driveletter -eq $CurDriveLetter},'default')
                    if ($curDrive -eq $null -or $curDrive.AllocUnit -eq $null -or $curDrive.AllocUnit -eq "") {
                        $DriveNumber = (Get-Partition -DriveLetter $CurDriveLetter).DiskNumber
                        if ($This.UseDriveLetterInVolumeName) {
                            Write-Verbose ("OnlineDisks:Set - Formatting drive $DriveNumber/$CurDriveLetter using default allocation unit using label " + ($CurDriveLetter + "_Drive"))
                            Format-Volume -DriveLetter $CurDriveLetter -FileSystem NTFS -NewFileSystemLabel ($CurDriveLetter + "_Drive") -Confirm:$false
                        } else {
                            Write-Verbose ("OnlineDisks:Set - Formatting drive $DriveNumber/$CurDriveLetter using default allocation unit using label " + ("Disk" + $DriveNumber))
                            Format-Volume -DriveLetter $CurDriveLetter -FileSystem NTFS -NewFileSystemLabel ("Disk" + $DriveNumber) -Confirm:$false
                        }
                    } else {
                        #Format with our allocation units
                        $DriveNumber = (Get-Partition -DriveLetter $CurDriveLetter).DiskNumber
                        if ($This.UseDriveLetterInVolumeName) {
                            Write-Verbose ("OnlineDisks:Set - Formatting drive $DriveNumber/$CurDriveLetter with allocation of " +  ([int]$curDrive.AllocUnit) + " kb using label " + ($CurDriveLetter + "_Drive")  )
                            Format-Volume -DriveLetter $CurDriveLetter -FileSystem NTFS -NewFileSystemLabel ($CurDriveLetter + "_Drive") `
                                        -AllocationUnitSize ( ([int]$curDrive.AllocUnit) * 1024) -Confirm:$false
                        } else {
                            Write-Verbose ("OnlineDisks:Set - Formatting drive $DriveNumber/$CurDriveLetter with allocation of " +  ([int]$curDrive.AllocUnit) + " kb using label " + ("Disk" + $DriveNumber) )
                            Format-Volume -DriveLetter $CurDriveLetter -FileSystem NTFS -NewFileSystemLabel ("Disk" + $DriveNumber) `
                                        -AllocationUnitSize ( ([int]$curDrive.AllocUnit) * 1024) -Confirm:$false
                        }
                    }
                    Write-Verbose "OnlineDisks:Set - Successfully formatted drive $DriveNumber/$CurDriveLetter"
                }

        } catch {
            $ErrorMessage = $_.Exception.Message
            Write-Verbose "ERROR: $ErrorMessage"
        } finally {
            Write-Verbose "OnlineDisks:Set - End"
        }
    }

    [string] GetNextDriveLetter() {
        Write-Debug  "GetNextDriveLetter - Start"
        try {
            $array2 = @()
            if ( $this.DiskLetterArray -and $this.DiskLetterArray.Count -gt 0) {
                #Use from our letter list
                $NextDriveLetter = $this.DiskLetterArray[0]
                #Move all the letters up one
                for ($i = 1; $i -lt $this.DiskLetterArray.Count; $i++) {
                    $array2 += $this.DiskLetterArray[$i]
                }
                $this.DiskLetterArray = $array2

                return $NextDriveLetter
            } else {
                $AllDrives = [char[]]([char]'C'..[char]'Z')
                $CurrentDriveList = (Get-Volume).DriveLetter
                $NextDriveLetter = $AllDrives | Where-Object { $CurrentDriveList -notcontains $_ } | Select-Object -First 1
                Write-Debug  "GetNextDriveLetter - Next Drive Letter '$NextDriveLetter'"
                return $NextDriveLetter
            }
        }
        finally {
            Write-Debug  "GetNextDriveLetter - End"
        }
    }

    [PSCustomObject] GetNextDisk () {
        Write-Debug  "GetNextDisk - Start"
        try {
            $CurrentDriveList = (Get-Volume).DriveLetter
            $RemainingDriveArray = $This.DriveArray.Where({$_.Driveletter -notin $CurrentDriveList},'default')
            if ( $RemainingDriveArray.Count -gt 0) {
                #Return our first Remaining Drive
                Write-Verbose ("GetNextDisk - Next Drive '" + $RemainingDriveArray[0] + "'")

                Return $RemainingDriveArray[0]
            } else {
                $AllDrives = [char[]]([char]'C'..[char]'Z')
                $CurrentDriveList = (Get-Volume).DriveLetter
                $NextDriveLetter = $AllDrives | Where-Object { $CurrentDriveList -notcontains $_ } | Select-Object -First 1
                Write-Verbose  "GetNextDisk - Next Drive Letter '$NextDriveLetter'"
                $NewDrive = [PSCustomObject]@{ 
                    DriveLetter = $NextDriveLetter
                    DriveSize = 0
                    AllocUnit = $Null
                }
                return $NewDrive
            }
        }
        finally {
            Write-Debug "GetNextDisk - End"
        } 
    }

} # This module defines a class for a DSC "cOnlineAllDisks" provider.