【问题标题】:Create ISO image using PowerShell: how to save IStream to file?使用 PowerShell 创建 ISO 映像:如何将 IStream 保存到文件?
【发布时间】:2012-01-09 13:53:10
【问题描述】:

我想在 Windows 上创建一个 ISO 映像,即 .iso 文件。使用 COM 组件 IMAPI2FS.MsftFileSystemImage 可以做到这一点,我在 an MSDN blog post entitled "Writing optical discs using IMAPI 2 in powershell" 中找到了有关如何使用 PowerShell 执行此操作的说明。

在第3步之后,那些说明说“在这一步你可以停止并将结果图像保存到本地硬盘,这将是一个纯iso图像。”

我的问题:如何在 PowerShell 中获取 $resultStream(即通过检索 ImageStream 产生的 COM 对象)并将其内容保存到文件中?

【问题讨论】:

    标签: com powershell imapi iso-image


    【解决方案1】:

    您需要使用 FileStream 编写器。检查此链接以获取如何在 c# 中完成的示例。 http://tools.start-automating.com/Install-ExportISOCommand/?-Download

    那里的功能可用于创建帮助您创建 ISO 的 cmdlet。例如,

    运行Install-ExportISOCommand 这将创建Export-Iso

    然后,使用Export-ISO 创建一个 ISO。

    Export-Iso -ISOPath C:\dropbox\test.iso -FileName C:\Dropbox\Scripts
    

    【讨论】:

    • 所以这意味着我需要(1)将IStream传递给.NET代码,其中数据类型大概是System.Runtime.InteropServices.ComTypes.IStream; (2) 一次读取该流一个块; (3) 使用FileStream.Write() 将这些块写入文件。对吗?
    • 会尝试(可能过几天),会在这里报告。
    • 我已经接受了这个作为答案。实际上,我想在不添加 cmdlet 的情况下执行此操作,并且不知何故在 PowerShell 中无法将 ImageStream 属性结果转换为 System.Runtime.InteropServices.ComTypes.IStream:该转换总是失败。所以我会从Export-ISO 窃取更多代码,看看是否效果更好。
    • 是否有机会发布工作代码。理想情况下,我想使用一个文件夹并从中创建一个 ISO,但使用纯 Win8 功能(即没有新模块或 cmdlet)。
    • Install-ExportISOCommand : The term 'Install-ExportISOCommand' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. At line:1 char:1 + Install-ExportISOCommand + ~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (Install-ExportISOCommand:String) [], CommandNotFoundException + FullyQualifiedErrorId : CommandNotFoundException
    【解决方案2】:

    您可以使用DiscUtils library 制作ISO并管理其他磁盘格式,例如 VHD/VHDX、DMG、FAT 等)。支持 PowerShell module 以及 MSBUILD task 以在项目构建时自动创建您的 ISO。

    创建一个CDBuilder 对象并通过添加文件和目录前往城镇,然后使用Build 方法将其保存到磁盘。 Download documentation here.

     CDBuilder builder = new CDBuilder();
     builder.AddFile("samplefile.txt", new byte[] { });
     builder.Build(@"c:\output.iso");
    

    这种方法的好处在于它是 100% 托管代码和跨平台 - 没有 IMAPI2 COM/Marshaling requirement

    【讨论】:

    • 谢谢。就我而言,我想避免额外的依赖,但这看起来像是一个非常易于使用的库。
    • 您可以使用 Nuget 安装程序将其快速添加到您的项目中...Install-Package Discutils
    【解决方案3】:

    这是一个端到端的 PowerShell ISO 创建器,带有我用过很多次的 GUI。无需额外的软件。

        # Author: Hrisan Dzhankardashliyski
    # Date: 20/05/2015
    
    # Inspiration from
    #
    #   http://blogs.msdn.com/b/opticalstorage/archive/2010/08/13/writing-optical-discs-using-imapi-2-in-powershell.aspx</a>
    #
    # and
    #
    #   http://tools.start-automating.com/Install-ExportISOCommand/</a>
    #
    # with help from
    #
    #   http://stackoverflow.com/a/9802807/223837</a>
    
    $InputFolder = ""
    
    function WriteIStreamToFile([__ComObject] $istream, [string] $fileName)
    {
    # NOTE: We cannot use [System.Runtime.InteropServices.ComTypes.IStream],
    # since PowerShell apparently cannot convert an IStream COM object to this
    # Powershell type.  (See <a href="http://stackoverflow.com/a/9037299/223837">http://stackoverflow.com/a/9037299/223837</a> for
    # details.)
    #
    # It turns out that .NET/CLR _can_ do this conversion.
    #
    # That is the reason why method FileUtil.WriteIStreamToFile(), below,
    # takes an object, and casts it to an IStream, instead of directly
    # taking an IStream inputStream argument.
    
    $cp = New-Object CodeDom.Compiler.CompilerParameters
    $cp.CompilerOptions = "/unsafe"
    $cp.WarningLevel = 4
    $cp.TreatWarningsAsErrors = $true
    
    Add-Type -CompilerParameters $cp -TypeDefinition @"
    using System;
    using System.IO;
    using System.Runtime.InteropServices.ComTypes;
    
    namespace My
    {
    
    public static class FileUtil {
    public static void WriteIStreamToFile(object i, string fileName) {
    IStream inputStream = i as IStream;
    FileStream outputFileStream = File.OpenWrite(fileName);
    int bytesRead = 0;
    int offset = 0;
    byte[] data;
    do {
    data = Read(inputStream, 2048, out bytesRead);
    outputFileStream.Write(data, 0, bytesRead);
    offset += bytesRead;
    } while (bytesRead == 2048);
    outputFileStream.Flush();
    outputFileStream.Close();
    }
    
    unsafe static private byte[] Read(IStream stream, int toRead, out int read) {
    byte[] buffer = new byte[toRead];
    int bytesRead = 0;
    int* ptr = &bytesRead;
    stream.Read(buffer, toRead, (IntPtr)ptr);
    read = bytesRead;
    return buffer;
    }
    }
    
    }
    "@
    
    [My.FileUtil]::WriteIStreamToFile($istream, $fileName)
    }
    
    # The Function defines the ISO parameturs and writes it to file
    function createISO([string]$VolName,[string]$Folder,[bool]$IncludeRoot,[string]$ISOFile){
    
    # Constants from <a href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa364840.aspx">http://msdn.microsoft.com/en-us/library/windows/desktop/aa364840.aspx</a>
    $FsiFileSystemISO9660   = 1
    $FsiFileSystemJoliet    = 2
    $FsiFileSystemUDF      = 4
    
    $fsi = New-Object -ComObject IMAPI2FS.MsftFileSystemImage
    
    #$fsi.FileSystemsToCreate = $FsiFileSystemISO9660 + $FsiFileSystemJoliet
    
    $fsi.FileSystemsToCreate = $FsiFileSystemUDF
    #When FreeMediaBlocks is set to 0 it allows the ISO file to be with unlimited size
    $fsi.FreeMediaBlocks = 0
    $fsi.VolumeName = $VolName
    
    $fsi.Root.AddTree($Folder, $IncludeRoot)
    
    WriteIStreamToFile $fsi.CreateResultImage().ImageStream $ISOFile
    }
    
    Function Get-Folder($initialDirectory)
    
    {
    [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
    
    $foldername = New-Object System.Windows.Forms.FolderBrowserDialog
    $foldername.rootfolder = "MyComputer"
    
    if($foldername.ShowDialog() -eq "OK")
    {
    $folder += [string]$foldername.SelectedPath
    }
    return $folder
    }
    
    # Show an Open Folder Dialog and return the directory selected by the user.
    function Read-FolderBrowserDialog([string]$Message, [string]$InitialDirectory, [switch]$NoNewFolderButton)
    {
    $browseForFolderOptions = 0
    if ($NoNewFolderButton) { $browseForFolderOptions += 512 }
    $app = New-Object -ComObject Shell.Application
    $folder = $app.BrowseForFolder(0, $Message, $browseForFolderOptions, $InitialDirectory)
    if ($folder) { $selectedDirectory = $folder.Self.Path }
    else { $selectedDirectory = '' }
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($app) > $null
    return $selectedDirectory
    }
    
    #Prompts the user to save the ISO file, if the files does not exists it will create it otherwise overwrite without prompt
    Function Get-SaveFile($initialDirectory)
    {
    [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |
    Out-Null
    
    $SaveFileDialog = New-Object System.Windows.Forms.SaveFileDialog
    $SaveFileDialog.CreatePrompt = $false
    $SaveFileDialog.OverwritePrompt = $false
    $SaveFileDialog.initialDirectory = $initialDirectory
    $SaveFileDialog.filter = "ISO files (*.iso)| *.iso"
    $SaveFileDialog.ShowHelp = $true
    $SaveFileDialog.ShowDialog() | Out-Null
    $SaveFileDialog.filename
    }
    
    # Show message box popup and return the button clicked by the user.
    function Read-MessageBoxDialog([string]$Message, [string]$WindowTitle, [System.Windows.Forms.MessageBoxButtons]$Buttons = [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]$Icon = [System.Windows.Forms.MessageBoxIcon]::None)
    {
    Add-Type -AssemblyName System.Windows.Forms
    return [System.Windows.Forms.MessageBox]::Show($Message, $WindowTitle, $Buttons, $Icon)
    }
    
    # GUI interface for the PowerShell script
    [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
    [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")  #loading the necessary .net libraries (using void to suppress output)
    
    $Form = New-Object System.Windows.Forms.Form    #creating the form (this will be the "primary" window)
    $Form.Text = "ISO Creator Tool:"
    $Form.Size = New-Object System.Drawing.Size(600,300)  #the size in px of the window length, height
    $Form.FormBorderStyle = 'FixedDialog'
    $Form.MaximizeBox = $false
    $Form.MinimizeBox = $false
    
    $objLabel = New-Object System.Windows.Forms.Label
    $objLabel.Location = New-Object System.Drawing.Size(20,20)
    $objLabel.Size = New-Object System.Drawing.Size(120,20)
    $objLabel.Text = "Please select a Folder:"
    $Form.Controls.Add($objLabel)
    
    $InputBox = New-Object System.Windows.Forms.TextBox
    $InputBox.Location = New-Object System.Drawing.Size(150,20)
    $InputBox.Size = New-Object System.Drawing.Size(300,20)
    $InputBox.Enabled = $false
    $Form.Controls.Add($InputBox)
    
    $objLabel2 = New-Object System.Windows.Forms.Label
    $objLabel2.Location = New-Object System.Drawing.Size(20,80)
    $objLabel2.Size = New-Object System.Drawing.Size(120,20)
    $objLabel2.Text = "ISO File Name:"
    $Form.Controls.Add($objLabel2)
    
    $InputBox2 = New-Object System.Windows.Forms.TextBox
    $InputBox2.Location = New-Object System.Drawing.Size(150,80)
    $InputBox2.Size = New-Object System.Drawing.Size(300,20)
    $InputBox2.Enabled = $false
    $Form.Controls.Add($InputBox2)
    
    $objLabel3 = New-Object System.Windows.Forms.Label
    $objLabel3.Location = New-Object System.Drawing.Size(20,50)
    $objLabel3.Size = New-Object System.Drawing.Size(120,20)
    $objLabel3.Text = "ISO Volume Name:"
    $Form.Controls.Add($objLabel3)
    
    $InputBox3 = New-Object System.Windows.Forms.TextBox
    $InputBox3.Location = New-Object System.Drawing.Size(150,50)
    $InputBox3.Size = New-Object System.Drawing.Size(150,20)
    $Form.Controls.Add($InputBox3)
    
    $objLabel4 = New-Object System.Windows.Forms.Label
    $objLabel4.Location = New-Object System.Drawing.Size(20,120)
    $objLabel4.Size = New-Object System.Drawing.Size(120,20)
    $objLabel4.Text = "Status Msg:"
    $Form.Controls.Add($objLabel4)
    
    $InputBox4 = New-Object System.Windows.Forms.TextBox
    $InputBox4.Location = New-Object System.Drawing.Size(150,120)
    $InputBox4.Size = New-Object System.Drawing.Size(200,20)
    $InputBox4.Enabled = $false
    $InputBox4.Text = "Set ISO Parameters..."
    $InputBox4.BackColor = "LightGray"
    $Form.Controls.Add($InputBox4)
    
    $Button = New-Object System.Windows.Forms.Button
    $Button.Location = New-Object System.Drawing.Size(470,20)
    $Button.Size = New-Object System.Drawing.Size(80,20)
    $Button.Text = "Browse"
    $Button.Add_Click({
    $InputBox.Text=Read-FolderBrowserDialog
    $InputBox4.Text = "Set ISO Parameters..."
    
    })
    $Form.Controls.Add($Button)
    
    $Button2 = New-Object System.Windows.Forms.Button
    $Button2.Location = New-Object System.Drawing.Size(470,120)
    $Button2.Size = New-Object System.Drawing.Size(80,80)
    $Button2.Text = "CreateISO"
    $Button2.Add_Click({
    
    if(($InputBox.Text -eq "") -or ($InputBox3.Text -eq "")){
    Read-MessageBoxDialog "You have to select folder and specify ISO Volume Name" "Error: No Parameters entered!"
    } else{
    $SaveDialog = Get-SaveFile
    #If you click cancel when save file dialog is called
    if ($SaveDialog -eq ""){
    return
    }
    $InputBox2.Text= $SaveDialog
    $InputBox2.Refresh()
    if($checkBox1.Checked){
    $includeRoot=$true
    }
    else{
    $includeRoot=$false
    }
    $InputBox4.BackColor = "Yellow"
    $InputBox4.Text = "Generating ISO file..."
    $InputBox4.Refresh()
    createISO $InputBox3.Text $InputBox.Text $includeRoot $InputBox2.Text
    $InputBox4.BackColor = "LimeGreen"
    $InputBox4.Text = "ISO Creation Finished!"
    $InputBox4.Refresh()
    }
    })
    $Form.Controls.Add($Button2)
    
    $objLabel5 = New-Object System.Windows.Forms.Label
    $objLabel5.Location = New-Object System.Drawing.Size(20,160)
    $objLabel5.Size = New-Object System.Drawing.Size(280,20)
    $objLabel5.Text = "Check the box if you want to include the top folder:"
    $Form.Controls.Add($objLabel5)
    
    $checkBox1 = New-Object System.Windows.Forms.CheckBox
    $checkBox1.Location = New-Object System.Drawing.Size(300,156)
    $Form.Controls.Add($checkBox1)
    
    $Form.Add_Shown({$Form.Activate()})
    [void] $Form.ShowDialog()
    

    【讨论】:

    • 这个答案更值得称赞。请收下这个奖杯,让我的一天变得更好?。
    猜你喜欢
    • 2017-07-12
    • 1970-01-01
    • 1970-01-01
    • 2011-07-01
    • 2023-02-02
    • 1970-01-01
    • 2017-03-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多