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-SizeToBytes()65 function Convert-SizeToBytes {
66     param(
67         $Size,
68         $Units
69     )
70 
71     switch ($Units) {
72         "B"   { return $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-AnsiblePartitionnull125 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     switch ($SizeMax) {
152         $True {
153             $parameters.Add("UseMaximumSize", $True)
154         }
155         $False {
156             $parameters.Add("Size", $Size)
157         }
158     }
159 
160     if ($null -ne $MbrType) {
161         $parameters.Add("MbrType", $Style)
162     }
163 
164     if ($null -ne $GptType) {
165         $parameters.Add("GptType", $Style)
166     }
167 
168     try {
169         $new_partition = New-Partition @parameters
170     } catch {
171         $module.FailJson("Unable to create a new partition: $($_.Exception.Message)", $_)
172     }
173 
174     return $new_partition
175 }
176 
177 
Set-AnsiblePartitionState()178 function Set-AnsiblePartitionState {
179     param(
180         $hidden,
181         $read_only,
182         $active,
183         $partition
184     )
185 
186     $parameters = @{
187         DiskNumber = $partition.DiskNumber
188         PartitionNumber = $partition.PartitionNumber
189     }
190 
191     if ($hidden -NotIn ($null, $partition.IsHidden)) {
192         $parameters.Add("IsHidden", $hidden)
193     }
194 
195     if ($read_only -NotIn ($null, $partition.IsReadOnly)) {
196         $parameters.Add("IsReadOnly", $read_only)
197     }
198 
199     if ($active -NotIn ($null, $partition.IsActive)) {
200         $parameters.Add("IsActive", $active)
201     }
202 
203     try {
204         Set-Partition @parameters
205     } catch {
206         $module.FailJson("Error changing state of partition: $($_.Exception.Message)", $_)
207     }
208 }
209 
210 
211 if ($ansible_partition) {
212     if ($state -eq "absent") {
213         try {
214             Remove-Partition -DiskNumber $ansible_partition.DiskNumber -PartitionNumber $ansible_partition.PartitionNumber -Confirm:$false -WhatIf:$module.CheckMode
215         } catch {
216             $module.FailJson("There was an error removing the partition: $($_.Exception.Message)", $_)
217         }
218         $module.Result.changed = $true
219     }
220     else {
221 
222         if ($null -ne $gpt_type -and $gpt_styles.$gpt_type -ne $ansible_partition.GptType) {
223             $module.FailJson("gpt_type is not a valid parameter for existing partitions")
224         }
225         if ($null -ne $mbr_type -and $mbr_styles.$mbr_type -ne $ansible_partition.MbrType) {
226             $module.FailJson("mbr_type is not a valid parameter for existing partitions")
227         }
228 
229         if ($partition_size) {
230             try {
231                 $max_supported_size = (Get-PartitionSupportedSize -DiskNumber $ansible_partition.DiskNumber -PartitionNumber $ansible_partition.PartitionNumber).SizeMax
232             } catch {
233                 $module.FailJson("Unable to get maximum supported partition size: $($_.Exception.Message)", $_)
234             }
235             if ($size_is_maximum) {
236                 $ansible_partition_size = $max_supported_size
237             }
238             if ($ansible_partition_size -ne $ansible_partition.Size -and $ansible_partition_size -le $max_supported_size) {
239                 if ($ansible_partition.IsReadOnly) {
240                     $module.FailJson("Unable to resize partition: Partition is read only")
241                 } else {
242                     try {
243                         Resize-Partition -DiskNumber $ansible_partition.DiskNumber -PartitionNumber $ansible_partition.PartitionNumber -Size $ansible_partition_size -WhatIf:$module.CheckMode
244                     } catch {
245                         $module.FailJson("Unable to change partition size: $($_.Exception.Message)", $_)
246                     }
247                     $module.Result.changed = $true
248                 }
249             } elseif ($ansible_partition_size -gt $max_supported_size) {
250                 $module.FailJson("Specified partition size exceeds size supported by the partition")
251             }
252         }
253 
254         if ($drive_letter -NotIn ("auto", $null, $ansible_partition.DriveLetter)) {
255             if (-not $module.CheckMode) {
256                 try {
257                     Set-Partition -DiskNumber $ansible_partition.DiskNumber -PartitionNumber $ansible_partition.PartitionNumber -NewDriveLetter $drive_letter
258                 } catch {
259                     $module.FailJson("Unable to change drive letter: $($_.Exception.Message)", $_)
260                 }
261             }
262             $module.Result.changed = $true
263         }
264     }
265 }
266 else {
267     if ($state -eq "present") {
268         if ($null -eq $disk_number) {
269             $module.FailJson("Missing required parameter: disk_number")
270         }
271         if ($null -eq $ansible_partition_size -and -not $size_is_maximum){
272             $module.FailJson("Missing required parameter: partition_size")
273         }
274         if (-not $size_is_maximum) {
275             try {
276                 $max_supported_size = (Get-Disk -Number $disk_number).LargestFreeExtent
277             } catch {
278                 $module.FailJson("Unable to get maximum size supported by disk: $($_.Exception.Message)", $_)
279             }
280 
281             if ($ansible_partition_size -gt $max_supported_size) {
282                 $module.FailJson("Partition size is not supported by disk. Use partition_size: -1 to get maximum size")
283             }
284         }
285 
286         $supp_part_type = (Get-Disk -Number $disk_number).PartitionStyle
287         if ($null -ne $mbr_type) {
288             if ($supp_part_type -eq "MBR" -and $mbr_styles.ContainsKey($mbr_type)) {
289                 $partition_style = $mbr_styles.$mbr_type
290             } else {
291                 $module.FailJson("Incorrect partition style specified")
292             }
293         }
294         if ($null -ne $gpt_type) {
295             if ($supp_part_type -eq "GPT" -and $gpt_styles.ContainsKey($gpt_type)) {
296                 $partition_style = $gpt_styles.$gpt_type
297             } else {
298                 $module.FailJson("Incorrect partition style specified")
299             }
300         }
301 
302         if (-not $module.CheckMode) {
303             $ansible_partition = New-AnsiblePartition -DiskNumber $disk_number -Letter $drive_letter -SizeMax $size_is_maximum -Size $ansible_partition_size -MbrType $mbr_type -GptType $gpt_type -Style $partition_style
304         }
305         $module.Result.changed = $true
306     }
307 }
308 
309 if ($state -eq "present" -and $ansible_partition) {
310     if ($offline -NotIn ($null, $ansible_partition.IsOffline)) {
311         if (-not $module.CheckMode) {
312             try {
313                 Set-Partition -DiskNumber $ansible_partition.DiskNumber -PartitionNumber $ansible_partition.PartitionNumber -IsOffline $offline
314             } catch {
315                 $module.FailJson("Error setting partition offline: $($_.Exception.Message)", $_)
316             }
317         }
318         $module.Result.changed = $true
319     }
320 
321     if ($hidden -NotIn ($null, $ansible_partition.IsHidden) -or $read_only -NotIn ($null, $ansible_partition.IsReadOnly) -or $active -NotIn ($null, $ansible_partition.IsActive)) {
322         if (-not $module.CheckMode) {
323             Set-AnsiblePartitionState -hidden $hidden -read_only $read_only -active $active -partition $ansible_partition
324         }
325         $module.Result.changed = $true
326     }
327 }
328 
329 $module.ExitJson()
330