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