hyperv-vm-provisioning
hyperv-vm-provisioning copied to clipboard
Switch to full PowerShell for ISO creation
I'm sorry that I'm doing this as an issue instead of a PR I'm just too lazy for a full PR but I still want to give this (hopefully) helpful code.
The below code creates an ISO completely in PowerShell, without relying on external software dependencies. I found this a while back floating around on the internet, and crudely modified it to make it work for cloud-init, you might appreciate to use this instead of relying on oscdimg.
function New-IsoFile {
<#
.Synopsis
Creates a new .iso file
.Description
The New-IsoFile cmdlet creates a new .iso file containing content from chosen folders
.Example
New-IsoFile "c:\tools","c:Downloads\utils"
This command creates a .iso file in $env:temp folder (default location) that contains c:\tools and c:\downloads\utils folders. The folders themselves are included at the root of the .iso image.
.Example
New-IsoFile -FromClipboard -Verbose
Before running this command, select and copy (Ctrl-C) files/folders in Explorer first.
.Example
dir c:\WinPE | New-IsoFile -Path c:\temp\WinPE.iso -BootFile "${env:ProgramFiles(x86)}\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\efisys.bin" -Media DVDPLUSR -Title "WinPE"
This command creates a bootable .iso file containing the content from c:\WinPE folder, but the folder itself isn't included. Boot file etfsboot.com can be found in Windows ADK. Refer to IMAPI_MEDIA_PHYSICAL_TYPE enumeration for possible media types: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366217(v=vs.85).aspx
.Notes
NAME: `
AUTHOR: Chris Wu
LASTEDIT: 03/23/2016 14:46:50
#>
[CmdletBinding(DefaultParameterSetName='Source')]Param(
[parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true, ParameterSetName='Source')]$Source,
[parameter(Position=2)][string]$Path = "$env:temp\$((Get-Date).ToString('yyyyMMdd-HHmmss.ffff')).iso",
[ValidateScript({Test-Path -LiteralPath $_ -PathType Leaf})][string]$BootFile = $null,
[ValidateSet('CDR','CDRW','DVDRAM','DVDPLUSR','DVDPLUSRW','DVDPLUSR_DUALLAYER','DVDDASHR','DVDDASHRW','DVDDASHR_DUALLAYER','DISK','DVDPLUSRW_DUALLAYER','BDR','BDRE')][string] $Media = 'DVDPLUSRW_DUALLAYER',
[string]$Title = (Get-Date).ToString("yyyyMMdd-HHmmss.ffff"),
[switch]$Force,
[parameter(ParameterSetName='Clipboard')][switch]$FromClipboard
)
Begin {
($cp = new-object System.CodeDom.Compiler.CompilerParameters).CompilerOptions = '/unsafe'
if (!('ISOFile' -as [type])) {
Add-Type -CompilerParameters $cp -TypeDefinition @'
public class ISOFile
{
public unsafe static void Create(string Path, object Stream, int BlockSize, int TotalBlocks)
{
int bytes = 0;
byte[] buf = new byte[BlockSize];
var ptr = (System.IntPtr)(&bytes);
var o = System.IO.File.OpenWrite(Path);
var i = Stream as System.Runtime.InteropServices.ComTypes.IStream;
if (o != null) {
while (TotalBlocks-- > 0) {
i.Read(buf, BlockSize, ptr); o.Write(buf, 0, bytes);
}
o.Flush(); o.Close();
}
}
}
'@
}
if ($BootFile) {
if('BDR','BDRE' -contains $Media) { Write-Warning "Bootable image doesn't seem to work with media type $Media" }
($Stream = New-Object -ComObject ADODB.Stream -Property @{Type=1}).Open() # adFileTypeBinary
$Stream.LoadFromFile((Get-Item -LiteralPath $BootFile).Fullname)
($Boot = New-Object -ComObject IMAPI2FS.BootOptions).AssignBootImage($Stream)
}
$MediaType = @('UNKNOWN','CDROM','CDR','CDRW','DVDROM','DVDRAM','DVDPLUSR','DVDPLUSRW','DVDPLUSR_DUALLAYER','DVDDASHR','DVDDASHRW','DVDDASHR_DUALLAYER','DISK','DVDPLUSRW_DUALLAYER','HDDVDROM','HDDVDR','HDDVDRAM','BDROM','BDR','BDRE')
Write-Verbose -Message "Selected media type is $Media with value $($MediaType.IndexOf($Media))"
($Image = New-Object -com IMAPI2FS.MsftFileSystemImage -Property @{VolumeName=$Title}).ChooseImageDefaultsForMediaType($MediaType.IndexOf($Media))
if (!($Target = New-Item -Path $Path -ItemType File -Force:$Force -ErrorAction SilentlyContinue)) { Write-Error -Message "Cannot create file $Path. Use -Force parameter to overwrite if the target file already exists."; break }
}
Process {
if($FromClipboard) {
if($PSVersionTable.PSVersion.Major -lt 5) { Write-Error -Message 'The -FromClipboard parameter is only supported on PowerShell v5 or higher'; break }
$Source = Get-Clipboard -Format FileDropList
}
foreach($sitem in $Source) {
if($sitem -isnot [System.IO.FileInfo] -and $item -isnot [System.IO.DirectoryInfo]) {
$sitems = Get-childItem -LiteralPath $sitem
}
foreach ($item in $sitems) {
if($item) {
Write-Verbose -Message "Adding item to the target image: $($item.FullName)"
try { $Image.Root.AddTree($item.FullName, $true) } catch { Write-Error -Message ($_.Exception.Message.Trim() + ' Try a different media type.') }
}
}
}
}
End {
if ($Boot) { $Image.BootImageOptions=$Boot }
$image.FileSystemsToCreate = 3
$Image.ISO9660InterchangeLevel = 2
$Image.GetDefaultFileSystemForImport(1)
$Image.UseRestrictedCharacterSet = $true
$Result = $Image.CreateResultImage()
[ISOFile]::Create($Target.FullName,$Result.ImageStream,$Result.BlockSize,$Result.TotalBlocks)
Write-Verbose -Message "Target image ($($Target.FullName)) has been created"
$Target
}
}
I did some changes to common version floating around, namely I hardcoded in that the ISO should specifically use the ISO9660 filesystem and not UDF as per the documentation this is required. The actual changes are $image.FileSystemsToCreate = 3
(link) and $Image.GetDefaultFileSystemForImport(1)
(link) I did some other minor changes but those most probably aren't really needed.
Why would we do that, is it better than using oscdimg?
BTW oscdimg is established official Microsoft tool so maybe still better choice since it is battle tested. We would still rely on other external binary dependancies (bsdtar, qemu-img) so this would not help us remove externals dependencies.
If you would like to merge this please make PR with following
- add function as new file to the project (external function/module) - like
New-IsoFile.ps1
- add parameter so that user can select which tool to use (default: oscdimg)
- if oscdimg selected but missing, write warning and fall back to your contributed new-isofile function
Hi @schtritoff
Your repo helped me out so I was just trying to reciprocate in kind.
I know oscdimg is an established and official tool to create images, I personally like to avoid shipping binaries with Powershell scripts since they add an external (avoidable) dependency, I figured you might also like to reduce an external dependency, I agree this doesn't remove all external dependencies, but every dependency less imho is better.
This above code relies on the build in windows imapi2 API which is a native windows API to create ISOs so it's not some complete homebrew code.
I specifically didn't create a PR since I don't have the time for this, but I still wanted to be helpful so I offered the code as is for you or anyone else to be able to make a PR.
Feel free to close this issue as is 😀
Thanks again for your amazing tool.