PowerShell 技能连载 - 挂载 ISO 文件

在我们之前的提示中,我们展示了如何轻松将本地文件夹转换为 ISO 文件镜像。今天,我们来看一下如何挂载(以及卸载)您自己和其他任何 ISO 文件,以便它们可以像本地文件系统驱动器一样使用。

挂载 ISO 文件很简单:

1
2
3
4
# 确保您调整此路径,使其指向现有的ISO文件:
$Path = "$env:temp\myImageFile.iso"
$result = Mount-DiskImage -ImagePath $Path -PassThru
$result

执行此代码后,Windows 资源管理器中会出现一个新的光驱,可以像其他驱动器一样使用。基于 ISO 镜像的驱动器当然是只读的,因为它们的行为就像常规的 CD-ROM。

虽然 Mount-DiskImage 可以成功挂载 ISO 镜像,但它不会将分配的驱动器字母返回给您。如果您想从脚本内部访问 ISO 镜像的内容,下面是如何找出它分配的驱动器字母:

1
2
3
4
5
6
7
8
9
# 确保您调整此路径,使其指向现有的ISO文件:
$Path = "$env:temp\myImageFile.iso"
$result = Mount-DiskImage -ImagePath $Path -PassThru
$result

$volume = $result | Get-Volume
$letter = $volume.Driveletter + ":\"

explorer $letter

在使用后卸载驱动器,请运行 Dismount-DiskImage 并指定您之前挂载的ISO文件的路径:

1
Dismount-DiskImage -ImagePath $Path

PowerShell 技能连载 - 创建 ISO 文件

PowerShell 可以将普通文件夹转换为 ISO 文件。ISO 文件是二进制文件,可以被挂载并表现得像只读 CD-ROM 驱动器。

过去,ISO 文件常用于挂载安装媒体。如今,您可以轻松地创建自己的 ISO 文件,这些文件是从您自己的文件夹和文件中创建的。这样,您可以创建一个简单的备份系统,或者轻松地在同事之间共享项目。由于 ISO 文件只是一个单一的文件,因此可以轻松地共享,而且由于 Windows 通过双击挂载它们,并在 Windows 资源管理器中显示它们作为 CD-ROM 驱动器,您可以立即使用数据而无需提取或解压任何内容。

与 VHD 映像文件不同,挂载 ISO 文件不需要管理员特权。任何人都可以挂载和使用 ISO 文件。

由于没有内置的 cmdlet 将文件夹结构转换为 ISO 文件,您需要自己调用内部 API。下面的代码定义了新函数 New-IsoFile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
function New-IsoFile
{
param
(
# path to local folder to store in
# new ISO file (must exist)
[Parameter(Mandatory)]
[String]
$SourceFilePath,

# name of new ISO image (arbitrary,
# turns later into drive label)
[String]
$ImageName = 'MyCDROM',

# path to ISO file to be created
[Parameter(Mandatory)]
[String]
$NewIsoFilePath,

# if specified, the source base folder is
# included into the image file
[switch]
$IncludeRoot
)

# use this COM object to create the ISO file:
$fsi = New-Object -ComObject IMAPI2FS.MsftFileSystemImage

# use this helper object to write a COM stream to a file:
# compile the helper code using these parameters:
$cp = [CodeDom.Compiler.CompilerParameters]::new()
$cp.CompilerOptions = '/unsafe'
$cp.WarningLevel = 4
$cp.TreatWarningsAsErrors = $true
$code = '
using System;
using System.IO;
using System.Runtime.InteropServices.ComTypes;

namespace CustomConverter
{
public static class Helper
{
// writes a stream that came from COM to a filesystem file
public static void WriteStreamToFile(object stream, string filePath)
{
// open output stream to new file
IStream inputStream = stream as IStream;
FileStream outputFileStream = File.OpenWrite(filePath);
int bytesRead = 0;
byte[] data;

// read stream in chunks of 2048 bytes and write to filesystem stream:
do
{
data = Read(inputStream, 2048, out bytesRead);
outputFileStream.Write(data, 0, bytesRead);
} while (bytesRead == 2048);

outputFileStream.Flush();
outputFileStream.Close();
}

// read bytes from stream:
unsafe static private byte[] Read(IStream stream, int byteCount, out int readCount)
{
// create a new buffer to hold the read bytes:
byte[] buffer = new byte[byteCount];
// provide a pointer to the location where the actually read bytes are reported:
int bytesRead = 0;
int* ptr = &bytesRead;
// do the read:
stream.Read(buffer, byteCount, (IntPtr)ptr);
// return the read bytes by reference to the caller:
readCount = bytesRead;
// return the read bytes to the caller:
return buffer;
}
}
}'

Add-Type -CompilerParameters $cp -TypeDefinition $code

# define the ISO file properties:

# create CDROM, Joliet and UDF file systems
$fsi.FileSystemsToCreate = 7
$fsi.VolumeName = $ImageName
# allow larger-than-CRRom-Sizes
$fsi.FreeMediaBlocks = -1

$msg = 'Creating ISO File - this can take a couple of minutes.'
Write-Host $msg -ForegroundColor Green

# define folder structure to be written to image:
$fsi.Root.AddTreeWithNamedStreams($SourceFilePath,$IncludeRoot.IsPresent)

# create image and provide a stream to read it:
$resultimage = $fsi.CreateResultImage()
$resultStream = $resultimage.ImageStream

# write stream to file
[CustomConverter.Helper]::WriteStreamToFile($resultStream, $NewIsoFilePath)

Write-Host 'DONE.' -ForegroundColor Green

}

运行此代码后,您现在将拥有一个名为“New-IsoFile”的新命令。从现有文件夹结构创建ISO文件现在变得轻而易举 - 只需确保源文件路径存在即可:

1
PS> New-IsoFile -NewIsoFilePath $env:temp\MyTest.iso -ImageName Holiday -SourceFilePath 'C:\HolidayPics'

您将在临时文件夹(或您指定的其他文件路径)中获得一个新的 ISO 文件。如果您按照示例操作,只需打开临时文件夹:

1
PS> explorer /select,$env:temp\MyTest.iso

当你在Windows资源管理器中双击ISO文件时,该映像将作为一个新的光驱挂载,你可以立即看到映像文件中存储的数据的副本。

在Windows资源管理器中右键单击新的光驱,并从上下文菜单中选择“弹出”将卸载该光驱。

PowerShell 技能连载 - 进度条技巧(第 4 部分)

由于广大用户的要求,这里提供了一段代码,演示如何使用嵌套进度条并显示每个任务的“真实”进度指示器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$servers = 'dc-01', 'dc-02', 'msv3', 'msv4'
$ports = 80, 445, 5985

$counterServers = 0
$servers | ForEach-Object {
# increment server counter and calculate progress
$counterServers++
$percentServers = $counterServers * 100 / $servers.Count

$server = $_
Write-Progress -Activity 'Checking Servers' -Status $server -Id 1 -PercentComplete $percentServers

$counterPorts = 0
$ports | ForEach-Object {
# increment port counter and calculate progress
$counterPorts++
$percentPorts = $counterPorts * 100 / $ports.Count


$port = $_
Write-Progress -Activity 'Checking Port' -Status $port -Id 2 -PercentComplete $percentPorts

# here would be your code that performs some task, i.e. a port test:
Start-Sleep -Seconds 1
}
}

PowerShell 技能连载 - 进度条技巧(第 3 部分)

PowerShell 内置的进度条可以嵌套,每个任务显示一个进度条。为了使其正常工作,请为您的进度条分配不同的 ID 号码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$servers = 'dc-01', 'dc-02', 'msv3', 'msv4'
$ports = 80, 445, 5985

$servers | ForEach-Object {
$server = $_
Write-Progress -Activity 'Checking Servers' -Status $server -Id 1

$ports | ForEach-Object {
$port = $_
Write-Progress -Activity 'Checking Port' -Status $port -Id 2

# here would be your code that performs some task, i.e. a port test:
Start-Sleep -Seconds 1
}
}

PowerShell 技能连载 - 进度条技巧(第 2 部分)

内置的 PowerShell 进度条支持“真实”的进度指示器,只要您提交一个在 0 到 100 范围内的“percentCompleted”值:

1
2
3
4
5
6
0..100 | ForEach-Object {
$message = '{0:p0} done' -f ($_/100)
Write-Progress -Activity 'I am busy' -Status $message -PercentComplete $_

Start-Sleep -Milliseconds 100
}

为了显示一个“真实”的进度指示器,因此您的脚本需要“知道”已经处理了多少给定任务。

以下是一个修改后的示例,它定义了需要处理多少个任务,然后从中计算出完成百分比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$data = Get-Service  # for illustration, let's assume you want to process all services

$counter = 0
$maximum = $data.Count # number of items to be processed

$data | ForEach-Object {
# increment counter
$counter++
$percentCompleted = $counter * 100 / $maximum
$message = '{0:p1} done, processing {1}' -f ($percentCompleted/100), $_.DisplayName
Write-Progress -Activity 'I am busy' -Status $message -PercentComplete $percentCompleted

Write-Host $message
Start-Sleep -Milliseconds 100
}

PowerShell 技能连载 - 进度条技巧(第 1 部分)

PowerShell自带内置进度条。通常情况下,当脚本完成时,它会自动消失:

1
2
3
4
Write-Progress -Activity 'I am busy' -Status 'Step A'
Start-Sleep -Seconds 2
Write-Progress -Activity 'I am busy' -Status 'Step B'
Start-Sleep -Seconds 2

如果您想在脚本仍在运行时关闭进度条,则需要使用“-Completed”开关参数:

1
2
3
4
5
6
7
Write-Progress -Activity 'I am busy' -Status 'Step A'
Start-Sleep -Seconds 2
Write-Progress -Activity 'I am busy' -Status 'Step B'
Start-Sleep -Seconds 2
Write-Progress -Completed -Activity 'I am busy'
Write-Host 'Progress bar closed, script still running.'
Start-Sleep -Seconds 2

如您所见,关闭进度条需要同时指定“-Activity”参数,因为它是一个强制性的参数。但是,如果您只想关闭所有可见的进度条,则“-Activity”参数的值并不重要。你可以提交一个空格或数字等任何值(除了null值或空字符串),因为这些都不会被强制性参数接受。

写入以下代码以定义“- Activity” 参数的默认值:

1
2
3
4
5
6
7
Write-Progress -Activity 'I am busy' -Status 'Step A'
Start-Sleep -Seconds 2
Write-Progress -Activity 'I am busy' -Status 'Step B'
Start-Sleep -Seconds 2
Write-Progress -Completed -Activity ' '
Write-Host 'Progress bar closed, script still running.'
Start-Sleep -Seconds 2

写入以下代码以定义 “-Activity“ 参数的默认值:

1
$PSDefaultParameterValues['Write-Progress:Activity']='xyz'

现在,”Write-progress“ 将接受 “Completed“ 参数而无需提交 “Activity“ 参数:

1
2
3
4
5
6
7
Write-Progress -Activity 'I am busy' -Status 'Step A'
Start-Sleep -Seconds 2
Write-Progress -Activity 'I am busy' -Status 'Step B'
Start-Sleep -Seconds 2
Write-Progress -Completed # due to the previously defined new default value, -Activity can now be omitted
Write-Host 'Progress bar closed, script still running.'
Start-Sleep -Seconds 2

PowerShell 技能连载 - 列出活动的域控制器

如果您的计算机连接到域,您可以使用 PowerShell 来识别您所连接的域控制器。可以使用以下命令:

1
Get-ADDomainController -Discover

或者,简单地查找 “LOGONSERVER” 环境变量:

1
$env:LOGONSERVER

它会列出您登录的计算机名称。如果它等于自己的计算机名称(不带反斜杠),则表示已本地登录而非加入域:

1
2
3
4
5
6
7
8
if ($env:LOGONSERVER.TrimStart('\') -eq $env:COMPUTERNAME)
{
"local"
}
else
{
"logged on to $env:LOGONSERVER"
}

PowerShell 技能连载 - 列出所有域控制器

要快速获取所有域控制器的列表,请运行以下命令:

1
Get-AdDomainController -Filter * | Select-Object -Property Name, Domain, Forest, IPv4Address, Site | Export-Csv -Path $env:temp\report.csv -UseCulture -NoTypeInformation -Encoding Default

当然,您需要登录到域,并且需要访问 “ActiveDirectory” PowerShell 模块。

该命令会在您的临时文件夹中创建一个 CSV 文件,可用 Excel 打开。只需双击所创建的 CSV 文件即可。”-UseCulture“ 确保 CSV 使用正确的分隔符以便 Excel 打开它。

PowerShell 技能连载 - 彻底删除硬盘数据

当您在硬盘驱动器或USB闪存等存储介质上删除文件时,如您所知,数据并不会立即被删除。相反,数据只是未分配的,并将根据需要被新数据覆盖。在此之前,任何人都可以恢复已删除的数据。

为了防止对已删除的数据进行访问,在 Windows 上可以使用内置工具 cipher.exe 显式地覆盖所有未分配的存储空间。当您这样做时,您会立即意识到为什么默认情况下不这样做:即使是所有零位的数据也需要很长时间才能存储到介质中。

该命令将三次覆盖 C:\ 驱动器上未分配的存储空间,首先用 “0”、然后用 “1”,最后用随机值。可能需要一个晚上才能完成这个任务:

1
cipher /w:C:\

以下是我们示例中使用的 “/w“ 开关的官方描述:“从整个卷中可用未使用磁盘空间中移除数据。如果选择此选项,则忽略所有其他选项。指定目录可以位于本地卷中任何位置。如果它是一个挂载点或指向另一个卷中目录,则将删除该卷上的数据。

PowerShell 技能连载 - 列出所有域控制器

要快速获取所有域控制器的列表,请运行以下命令:

1
Get-AdDomainController -Filter * | Select-Object -Property Name, Domain, Forest, IPv4Address, Site | Export-Csv -Path $env:temp\report.csv -UseCulture -NoTypeInformation -Encoding Default

当然,您需要登录到域,并且需要访问“ActiveDirectory” PowerShell 模块。

该命令会在您的临时文件夹中创建一个 CSV 文件,可用 Excel 打开。只需双击创建的 CSV 文件即可。“-UseCulture”确保CSV使用正确的分隔符以便 Excel 打开它。