1 #!powershell
2 
3 # Copyright: (c) 2018, Varun Chopra (@chopraaa) <v@chopraaa.com>
4 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5 
6 #AnsibleRequires -CSharpUtil Ansible.Basic
7 #AnsibleRequires -OSVersion 6.2
8 
9 Set-StrictMode -Version 2
10 
11 $ErrorActionPreference = "Stop"
12 
13 $spec = @{
14     options = @{
15         state = @{ type = "str"; choices = "absent", "present"; default = "present" }
16         drive_letter = @{ type = "str" }
17         disk_number = @{ type = "int" }
18         partition_number = @{ type = "int" }
19         partition_size = @{ type = "str" }
20         read_only = @{ type = "bool" }
21         active = @{ type = "bool" }
22         hidden = @{ type = "bool" }
23         offline = @{ type = "bool" }
24         mbr_type = @{ type = "str"; choices = "fat12", "fat16", "extended", "huge", "ifs", "fat32" }
25         gpt_type = @{ type = "str"; choices = "system_partition", "microsoft_reserved", "basic_data", "microsoft_recovery" }
26     }
27     supports_check_mode = $true
28 }
29 
30 $module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
31 
32 $state = $module.Params.state
33 $drive_letter = $module.Params.drive_letter
34 $disk_number = $module.Params.disk_number
35 $partition_number = $module.Params.partition_number
36 $partition_size = $module.Params.partition_size
37 $read_only = $module.Params.read_only
38 $active = $module.Params.active
39 $hidden = $module.Params.hidden
40 $offline = $module.Params.offline
41 $mbr_type = $module.Params.mbr_type
42 $gpt_type = $module.Params.gpt_type
43 
44 $size_is_maximum = $false
45 $ansible_partition = $false
46 $ansible_partition_size = $null
47 $partition_style = $null
48 
49 $gpt_styles = @{
50     system_partition = "{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}"
51     microsoft_reserved = "{e3c9e316-0b5c-4db8-817d-f92df00215ae}"
52     basic_data = "{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}"
53     microsoft_recovery = "{de94bba4-06d1-4d40-a16a-bfd50179d6ac}"
54 }
55 
56 $mbr_styles = @{
57     fat12 = 1
58     fat16 = 4
59     extended = 5
60     huge = 6
61     ifs = 7
62     fat32 = 12
63 }
64 
Convert-SizeToBytesnull65 function Convert-SizeToBytes {
66     param(
67         $Size,
68         $Units
69     )
70 
71     switch ($Units) {
72         "B"   { return 1 * $Size }
73         "KB"  { return 1000 * $Size }
74         "KiB" { return 1024 * $Size }
75         "MB"  { return [Math]::Pow(1000, 2) * $Size }
76         "MiB" { return [Math]::Pow(1024, 2) * $Size }
77         "GB"  { return [Math]::Pow(1000, 3) * $Size }
78         "GiB" { return [Math]::Pow(1024, 3) * $Size }
79         "TB"  { return [Math]::Pow(1000, 4) * $Size }
80         "TiB" { return [Math]::Pow(1024, 4) * $Size }
81     }
82 }
83 
84 if ($null -ne $partition_size) {
85     if ($partition_size -eq -1) {
86         $size_is_maximum = $true
87     }
88     elseif ($partition_size -match '^(?<Size>[0-9]+)[ ]*(?<Units>b|kb|kib|mb|mib|gb|gib|tb|tib)$') {
89         $ansible_partition_size = Convert-SizeToBytes -Size $Matches.Size -Units $Matches.Units
90     }
91     else {
92         $module.FailJson("Invalid partition size. B, KB, KiB, MB, MiB, GB, GiB, TB, TiB are valid partition size units")
93     }
94 }
95 
96 # If partition_exists, we can change or delete it; otherwise we only need the disk to create a new partition
97 if ($null -ne $disk_number -and $null -ne $partition_number) {
98     $ansible_partition = Get-Partition -DiskNumber $disk_number -PartitionNumber $partition_number -ErrorAction SilentlyContinue
99 }
100 # Check if drive_letter is either auto-assigned or a character from A-Z
101 elseif ($drive_letter -and -not ($disk_number -and $partition_number)) {
102     if ($drive_letter -eq "auto" -or $drive_letter -match "^[a-zA-Z]$") {
103         $ansible_partition = Get-Partition -DriveLetter $drive_letter -ErrorAction SilentlyContinue
104     }
105     else {
106         $module.FailJson("Incorrect usage of drive_letter: specify a drive letter from A-Z or use 'auto' to automatically assign a drive letter")
107     }
108 }
109 elseif ($disk_number) {
110     try {
111         Get-Disk -Number $disk_number | Out-Null
112     } catch {
113         $module.FailJson("Specified disk does not exist")
114     }
115 }
116 else {
117     $module.FailJson("You must provide disk_number, partition_number")
118 }
119 
120 # Partition can't have two partition styles
121 if ($null -ne $gpt_type -and $null -ne $mbr_type) {
122     $module.FailJson("Cannot specify both GPT and MBR partition styles. Check which partition style is supported by the disk")
123 }
124 
New-AnsiblePartition()125 function New-AnsiblePartition {
126     param(
127         $DiskNumber,
128         $Letter,
129         $SizeMax,
130         $Size,
131         $MbrType,
132         $GptType,
133         $Style
134     )
135 
136     $parameters = @{
137         DiskNumber = $DiskNumber
138     }
139 
140     if ($null -ne $Letter) {
141         switch ($Letter) {
142             "auto" {
143                 $parameters.Add("AssignDriveLetter", $True)
144             }
145             default {
146                 $parameters.Add("DriveLetter", $Letter)
147             }
148         }
149     }
150 
151     if ($null -ne $Size) {
152         $parameters.Add("Size", $Size)
153     }
154 
155     if ($null -ne $MbrType) {
156         $parameters.Add("MbrType", $Style)
157     }
158 
159     if ($null -ne $GptType) {
160         $parameters.Add("GptType", $Style)
161     }
162 
163     try {
164         $new_partition = New-Partition @parameters
165     } catch {
166         $module.FailJson("Unable to create a new partition: $($_.Exception.Message)", $_)
167     }
168 
169     return $new_partition
170 }
171 
172 
Set-AnsiblePartitionState()173 function Set-AnsiblePartitionState {
174     param(
175         $hidden,
176         $read_only,
177         $active,
178         $partition
179     )
180 
181     $parameters = @{
182         DiskNumber = $partition.DiskNumber
183         PartitionNumber = $partition.PartitionNumber
184     }
185 
186     if ($hidden -NotIn ($null, $partition.IsHidden)) {
187         $parameters.Add("IsHidden", $hidden)
188     }
189 
190     if ($read_only -NotIn ($null, $partition.IsReadOnly)) {
191         $parameters.Add("IsReadOnly", $read_only)
192     }
193 
194     if ($active -NotIn ($null, $partition.IsActive)) {
195         $parameters.Add("IsActive", $active)
196     }
197 
198     try {
199         Set-Partition @parameters
200     } catch {
201         $module.FailJson("Error changing state of partition: $($_.Exception.Message)", $_)
202     }
203 }
204 
205 
206 if ($ansible_partition) {
207     if ($state -eq "absent") {
208         try {
209             Remove-Partition -DiskNumber $ansible_partition.DiskNumber -PartitionNumber $ansible_partition.PartitionNumber -Confirm:$false -WhatIf:$module.CheckMode
210         } catch {
211             $module.FailJson("There was an error removing the partition: $($_.Exception.Message)", $_)
212         }
213         $module.Result.changed = $true
214     }
215     else {
216 
217         if ($null -ne $gpt_type -and $gpt_styles.$gpt_type -ne $ansible_partition.GptType) {
218             $module.FailJson("gpt_type is not a valid parameter for existing partitions")
219         }
220         if ($null -ne $mbr_type -and $mbr_styles.$mbr_type -ne $ansible_partition.MbrType) {
221             $module.FailJson("mbr_type is not a valid parameter for existing partitions")
222         }
223 
224         if ($partition_size) {
225             try {
226                 $max_supported_size = (Get-PartitionSupportedSize -DiskNumber $ansible_partition.DiskNumber -PartitionNumber $ansible_partition.PartitionNumber).SizeMax
227             } catch {
228                 $module.FailJson("Unable to get maximum supported partition size: $($_.Exception.Message)", $_)
229             }
230             if ($size_is_maximum) {
231                 $ansible_partition_size = $max_supported_size
232             }
233             if ($ansible_partition_size -ne $ansible_partition.Size -and ($ansible_partition_size - $ansible_partition.Size -gt 1049000 -or $ansible_partition.Size - $ansible_partition_size -gt 1049000)) {
234                 if ($ansible_partition.IsReadOnly) {
235                     $module.FailJson("Unable to resize partition: Partition is read only")
236                 } else {
237                     try {
238                         Resize-Partition -DiskNumber $ansible_partition.DiskNumber -PartitionNumber $ansible_partition.PartitionNumber -Size $ansible_partition_size -WhatIf:$module.CheckMode
239                     } catch {
240                         $module.FailJson("Unable to change partition size: $($_.Exception.Message)", $_)
241                     }
242                     $module.Result.changed = $true
243                 }
244             } elseif ($ansible_partition_size -gt $max_supported_size) {
245                 $module.FailJson("Specified partition size exceeds size supported by the partition")
246             }
247         }
248 
249         if ($drive_letter -NotIn ("auto", $null, $ansible_partition.DriveLetter)) {
250             if (-not $module.CheckMode) {
251                 try {
252                     Set-Partition -DiskNumber $ansible_partition.DiskNumber -PartitionNumber $ansible_partition.PartitionNumber -NewDriveLetter $drive_letter
253                 } catch {
254                     $module.FailJson("Unable to change drive letter: $($_.Exception.Message)", $_)
255                 }
256             }
257             $module.Result.changed = $true
258         }
259     }
260 }
261 else {
262     if ($state -eq "present") {
263         if ($null -eq $disk_number) {
264             $module.FailJson("Missing required parameter: disk_number")
265         }
266         if ($null -eq $ansible_partition_size -and -not $size_is_maximum){
267             $module.FailJson("Missing required parameter: partition_size")
268         }
269         if (-not $size_is_maximum) {
270             try {
271                 $max_supported_size = (Get-Disk -Number $disk_number).LargestFreeExtent
272             } catch {
273                 $module.FailJson("Unable to get maximum size supported by disk: $($_.Exception.Message)", $_)
274             }
275 
276             if ($ansible_partition_size -gt $max_supported_size) {
277                 $module.FailJson("Partition size is not supported by disk. Use partition_size: -1 to get maximum size")
278             }
279         } else {
280             $ansible_partition_size = (Get-Disk -Number $disk_number).LargestFreeExtent
281         }
282 
283         $supp_part_type = (Get-Disk -Number $disk_number).PartitionStyle
284         if ($null -ne $mbr_type) {
285             if ($supp_part_type -eq "MBR" -and $mbr_styles.ContainsKey($mbr_type)) {
286                 $partition_style = $mbr_styles.$mbr_type
287             } else {
288                 $module.FailJson("Incorrect partition style specified")
289             }
290         }
291         if ($null -ne $gpt_type) {
292             if ($supp_part_type -eq "GPT" -and $gpt_styles.ContainsKey($gpt_type)) {
293                 $partition_style = $gpt_styles.$gpt_type
294             } else {
295                 $module.FailJson("Incorrect partition style specified")
296             }
297         }
298 
299         if (-not $module.CheckMode) {
300             $ansible_partition = New-AnsiblePartition -DiskNumber $disk_number -Letter $drive_letter -Size $ansible_partition_size -MbrType $mbr_type -GptType $gpt_type -Style $partition_style
301         }
302         $module.Result.changed = $true
303     }
304 }
305 
306 if ($state -eq "present" -and $ansible_partition) {
307     if ($offline -NotIn ($null, $ansible_partition.IsOffline)) {
308         if (-not $module.CheckMode) {
309             try {
310                 Set-Partition -DiskNumber $ansible_partition.DiskNumber -PartitionNumber $ansible_partition.PartitionNumber -IsOffline $offline
311             } catch {
312                 $module.FailJson("Error setting partition offline: $($_.Exception.Message)", $_)
313             }
314         }
315         $module.Result.changed = $true
316     }
317 
318     if ($hidden -NotIn ($null, $ansible_partition.IsHidden) -or $read_only -NotIn ($null, $ansible_partition.IsReadOnly) -or $active -NotIn ($null, $ansible_partition.IsActive)) {
319         if (-not $module.CheckMode) {
320             Set-AnsiblePartitionState -hidden $hidden -read_only $read_only -active $active -partition $ansible_partition
321         }
322         $module.Result.changed = $true
323     }
324 }
325 
326 $module.ExitJson()
327