I'm using terraform and chef to create multiple aws ebs volumes and attach them to an EC2 instance.
The problem is I want to be able to give each ebs volume a specific windows drive letter. The problem is when the EC2 instance is instantiated window just gives it sequential drive letters (D,E,F,etc)
Some of the drives are identically sized so I can't necessarily rename based on drive size. Does anyone know of a way to do this with terraform or chef. My google foo isn't finding anything.
Certainly this must come up for other folks?
I did see reference to using EC2Config Windows GUI to set them but the whole point is to automate the process, as ultimately I want chef to install SQL server and certain data is expected to go on certain drive letters.
This seems to work - although I do wonder if there isn't an easier way.
function Convert-SCSITargetIdToDeviceName
{
param([int]$SCSITargetId)
If ($SCSITargetId -eq 0) {
return "/dev/sda1"
}
$deviceName = "xvd"
If ($SCSITargetId -gt 25) {
$deviceName += [char](0x60 + [int]($SCSITargetId / 26))
}
$deviceName += [char](0x61 + $SCSITargetId % 26)
return $deviceName
}
Get-WmiObject -Class Win32_DiskDrive | ForEach-Object {
$DiskDrive = $_
$Volumes = Get-WmiObject -Query "ASSOCIATORS OF {Win32_DiskDrive.DeviceID='$($DiskDrive.DeviceID)'} WHERE AssocClass=Win32_DiskDriveToDiskPartition" | ForEach-Object {
$DiskPartition = $_
Get-WmiObject -Query "ASSOCIATORS OF {Win32_DiskPartition.DeviceID='$($DiskPartition.DeviceID)'} WHERE AssocClass=Win32_LogicalDiskToPartition"
}
If ($DiskDrive.PNPDeviceID -like "*PROD_PVDISK*") {
$BlockDeviceName = Convert-SCSITargetIdToDeviceName($DiskDrive.SCSITargetId)
If ($BlockDeviceName -eq "xvdf") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="D:"; Label="SQL Data"} };
If ($BlockDeviceName -eq "xvdg") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="L:"; Label="SQL Logs"} };
If ($BlockDeviceName -eq "xvdh") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="R:"; Label="Report Data"} };
If ($BlockDeviceName -eq "xvdi") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="T:"; Label="Temp DB"} };
If ($BlockDeviceName -eq "xvdj") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="M:"; Label="MSDTC"} };
If ($BlockDeviceName -eq "xvdk") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="B:"; Label="Backups"} };
} ElseIf ($DiskDrive.PNPDeviceID -like "*PROD_AMAZON_EC2_NVME*") {
$BlockDeviceName = Get-EC2InstanceMetadata "meta-data/block-device-mapping/ephemeral$($DiskDrive.SCSIPort - 2)"
If ($BlockDeviceName -eq "xvdf") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="D:"; Label="SQL Data"} };
If ($BlockDeviceName -eq "xvdg") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="L:"; Label="SQL Logs"} };
If ($BlockDeviceName -eq "xvdh") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="R:"; Label="Report Data"} };
If ($BlockDeviceName -eq "xvdi") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="T:"; Label="Temp DB"} };
If ($BlockDeviceName -eq "xvdj") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="M:"; Label="MSDTC"} };
If ($BlockDeviceName -eq "xvdk") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="B:"; Label="Backups"} };
} Else {
write-host "Couldn't find disks";
}
}
If you take the tables in this link into account: https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/ec2-windows-volumes.html
You can see that on EBS, the first rows are:
Disk 0 (/dev/sda1) is always setup for you by EC2 as C:
So you know when you run "New-Partition -DiskNumber 1 -UseMaximumSize -IsActive -AssignDriveLetter" you are going to get D: given to it.
So if you provision an AMI image with Packer using the following volumes in Builders (just two here in this example but you could do however many):
..You can plan, knowing xvd[b] is actually two letters behind what will get mapped.
Then spin up an EC2 instance of this multi-volume AMI with Terraform and have this in the user_data section of the aws_instance resource:
The
Set-Partition -DriveLetter D -NewDriveLetter Sline(s) is what you use to rename your known sequential drive(s) to whatever letter(s) you are used to. In my case, they wanted D: as S: - just repeat this line to rename E: as X: or whatever you need.Hope this helps.
UPDATE: There is another way (Server 2016 up), which I discovered when I discovered Sysprep nukes all the mappings being baked into the AMI image.
You have to provide a DriveLetterMappingConfig.json file in C:\ProgramData\Amazon\EC2-Windows\Launch\Config to do the mapping. The format of the file is:
...Only, my drives, by default, didn't have a volumeName; they were blank. So back to the 1980-something good old "LABEL" command. Labeled the D: drive as volume2. So the file looks like:
Running C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeDisks.ps1 tested this worked (D: became S:)
So now, back in Packer, I need to also provision the image with this DriveLetterMappingConfig.json file in C:\ProgramData\Amazon\EC2-Windows\Launch\Config to make sure all the drive work I did on the AMI's S: comes back as S: on the instance. (I put the file in an S3 bucket along with all the other crap we are going to install on the box.)
I put the disk stuff into a .ps1 and call it from a provisioner:
{ "type": "powershell", "script": "./setup_two_drive_names_c_and_s.ps1"
},
Where the above .ps1 is:
Yeah, there is no reason to label c: But I was on a roll...
The final line with the "-Schedule" parameter means this happens on every boot.