PwSh.Fw.Iso.psm1

<#
 
    .SYNOPSIS
    Module to export ISO function found here and there.
 
    .DESCRIPTION
 
    .NOTES
        Author: Charles-Antoine Degennes <cadegenn@gmail.com>
        Version: 0.0.1
        Changelog:
            2017.09.25, DCA - inital version
        Manifest created with command line:
        New-ModuleManifest iso.psd1 -RootModule iso.psm1 -ModuleVersion "0.0.1" -Author "Charles-Antoine Degennes <cadegenn@gmail.com>"
 
#>



function New-IsoFileWindows
{
  <#
   .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: New-IsoFile
    AUTHOR: Chris Wu
    LASTEDIT: 03/23/2016 14:46:50
    .LINK
    https://gallery.technet.microsoft.com/scriptcenter/New-ISOFile-function-a8deeffd
  .LINK
  https://github.com/wikijm/PowerShell-AdminScripts/blob/master/Miscellaneous/New-IsoFile.ps1
 #>


  [CmdletBinding(DefaultParameterSetName='Source', SupportsShouldProcess = $true)]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Windows is not a plural noun, it is the name of the platform.")]
  Param(
    [parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true, ParameterSetName='Source')]$Source,
    [Alias('Destination')]
    [parameter(Position=2)][string]$Path = "$([io.path]::GetTempPath())/$((Get-Date).ToString('yyyyMMdd-HHmmss.ffff')).iso",
    [ValidateScript({Test-Path -LiteralPath $_ -PathType Leaf})][string]$BootFile = $null,
    [ValidateSet('UNKNOWN','CDROM','CDR','CDRW','DVDROM','DVDRAM','DVDPLUSR','DVDPLUSRW','DVDPLUSR_DUALLAYER','DVDDASHR','DVDDASHRW','DVDDASHR_DUALLAYER','DISK','DVDPLUSRW_DUALLAYER','HDDVDROM','HDDVDR','HDDVDRAM','BDROM','BDR','BDRE')]
    [string]$Media = 'DVDPLUSRW_DUALLAYER',
    [string]$Title = (Get-Date).ToString("yyyyMMdd-HHmmss.ffff"),
    # return object
    [switch]$PassThru,

    [switch]$Force,
    [parameter(ParameterSetName='Clipboard')][switch]$FromClipboard
  )

  Begin {
    Write-EnterFunction
    # ($cp = new-object System.CodeDom.Compiler.CompilerParameters).CompilerOptions = '/unsafe'
    # if (!('ISOFile' -as [type])) {
    # Add-Type -CompilerParameters $cp -TypeDefinition @'
    if (!('ISOFile' -as [type])) {
      Add-Type -CompilerOptions '-unsafe' -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))
    # détailler ce one-liner grâce aux infos vues sur @url https://gist.github.com/marnix/3944688
    <#
    # Constants from http://msdn.microsoft.com/en-us/library/windows/desktop/aa364840.aspx
    $FsiFileSystemISO9660 = 1
    $FsiFileSystemJoliet = 2
  $Image = New-Object -com IMAPI2FS.MsftFileSystemImage
    $Image.VolumeName = $Title
    $Image.FileSystemsToCreate = $FsiFileSystemISO9660 + $FsiFileSystemJoliet
    #>


    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($item in $Source) {
      if($item -isnot [System.IO.FileInfo] -and $item -isnot [System.IO.DirectoryInfo]) {
        $item = Get-Item -LiteralPath $item
      }

      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 }
    if ($PSCmdlet.ShouldProcess("Create ISO")) {
      $Result = $Image.CreateResultImage()
      [ISOFile]::Create($Target.FullName,$Result.ImageStream,$Result.BlockSize,$Result.TotalBlocks)
      Write-Verbose -Message "Target image ($($Target.FullName)) has been created"
    }
    Write-LeaveFunction
    if ($PassThru) {
      return (Get-Item $Path)
    }
  }
}

<#
.SYNOPSIS
Create an iso file using linux way
 
.NOTES
General notes
#>

function New-IsoFileLinux {
  [CmdletBinding(SupportsShouldProcess = $true)]
  [OutputType([String])]
  Param (
    # Source folder
    [parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true, ParameterSetName='Source')]$Source,

    # Destination filename
    [Alias('Destination')]
    [parameter(Position=2)][string]$Path = "$([io.path]::GetTempPath())/$((Get-Date).ToString('yyyyMMdd-HHmmss.ffff')).iso",

    # optional boot file
    [ValidateScript({Test-Path -LiteralPath $_ -PathType Leaf})][string]$BootFile = $null,

    # Title of the iso when mounted
    [string]$Title = (Get-Date).ToString("yyyyMMdd-HHmmss.ffff"),

    # return object
    [switch]$PassThru,

    # force things
    [switch]$Force
  )
  Begin {
    Write-EnterFunction
  }

  Process {
    if ($BootFile) { $CMDLINE += " -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table"}
    if ($PSCmdlet.ShouldProcess("Create ISO")) {
      if (Test-FileExist $Path) {
        if ($Force) {
          $null = Remove-Item $Path
        } else {
          Write-Error "File '$Path' already exist. Delete it or use -Force to overwrite."
          return ""
        }
      }
      Execute-Command -exe "mkisofs" -args "$CMDLINE -input-charset utf-8 -J -r -V $Title -o $Path $Source"
    }
    if ($PassThru) {
      return (Get-Item $Path)
    }
  }

  End {
    Write-LeaveFunction
  }
}

<#
.SYNOPSIS
Create an iso file using macos way
 
.NOTES
General notes
#>

function New-IsoFileMacOS {
  [CmdletBinding(SupportsShouldProcess = $true)]
  [OutputType([String])]
  Param (
    # Source folder
    [parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true, ParameterSetName='Source')]$Source,

    # Destination filename
    [Alias('Destination')]
    [parameter(Position=2)][string]$Path = "$([io.path]::GetTempPath())/$((Get-Date).ToString('yyyyMMdd-HHmmss.ffff')).iso",

    # optional boot file
    [ValidateScript({Test-Path -LiteralPath $_ -PathType Leaf})][string]$BootFile = $null,

    # Title of the iso when mounted
    [string]$Title = (Get-Date).ToString("yyyyMMdd-HHmmss.ffff"),

    # return object
    [switch]$PassThru,

    # force things
    [switch]$Force
  )
  Begin {
    Write-EnterFunction
  }

  Process {
    if ($PSCmdlet.ShouldProcess("Create ISO")) {
      if (Test-FileExist $Path) {
        if ($Force) {
          $null = Remove-Item $Path
        } else {
          Write-Error "File '$Path' already exist. Delete it or use -Force to overwrite."
          return ""
        }
      }
      Execute-Command -exe "hdiutil" -args "makehybrid -iso -joliet -o $Path $Source"
    }
    if ($PassThru) {
      return (Get-Item $Path)
    }
  }

  End {
    Write-LeaveFunction
  }
}

<#
.SYNOPSIS
Wrapper around New-IsoFile* to abstract platform
 
.DESCRIPTION
Call correct New-ISOFile{{PLATFORM}} with all Parameters
 
.NOTES
General notes
#>

function New-IsoFile {
  [CmdletBinding(SupportsShouldProcess = $true)]
  [OutputType([String])]
  Param (
    # Source folder
    [parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true, ParameterSetName='Source')]$Source,

    # Destination filename
    [Alias('Destination')]
    [parameter(Position=2)][string]$Path = "$([io.path]::GetTempPath())/$((Get-Date).ToString('yyyyMMdd-HHmmss.ffff')).iso",

    # optional boot file
    [ValidateScript({Test-Path -LiteralPath $_ -PathType Leaf})][string]$BootFile = $null,

    # Title of the iso when mounted
    [string]$Title = (Get-Date).ToString("yyyyMMdd-HHmmss.ffff"),

    # return object
    [switch]$PassThru,

    # force things
    [switch]$Force
  )
  Begin {
    Write-EnterFunction
  }

  Process {
    if ($IsLinux) { $isoFile = New-IsoFileLinux @PSBoundParameters }
    if ($IsMacOS) { $isoFile = New-IsoFileMacOS @PSBoundParameters }
    if ($IsWindows) { $isoFile = New-IsoFileWindows @PSBoundParameters }
    if ($PassThru) {
      return $isoFile
    }
  }

  End {
    Write-LeaveFunction
  }
}