首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >腾讯云自定义镜像为何不支持购买cvm时将一并加购的数据盘初始化

腾讯云自定义镜像为何不支持购买cvm时将一并加购的数据盘初始化

原创
作者头像
Windows技术交流
修改2025-09-04 18:06:26
修改2025-09-04 18:06:26
37700
代码可运行
举报
文章被收录于专栏:Windows技术交流Windows技术交流
运行总次数:0
代码可运行

需求:想要购买cvm时初始化数据盘

解决方案:购买cvm的时候,选公共镜像,在cvm购买界面上一并加购数据盘并勾选初始化复选框,然后就会自动化分好区;自定义镜像不行,平台的考虑是:自定义镜像千差万别,平台无法确认客户自定义镜像的init环境,因此前端才没设计那个勾选按钮,不过初始化数据盘本质上是通过init的userdata起作用的,理论上在userdata里传入选公共镜像时勾选初始化数据盘后userdata部分的代码的话,自定义镜像也可以实现初始化数据盘,前提是自定义镜像的init是ok的(linux对应cloudinit,windows对应cloudbase-init)

优化后的windows数据盘初始化代码如下(仅供参考,风险自担

原本代码里没有匹配上大小是TB的情况,所以大盘不会被初始化,然后我加了匹配大盘的逻辑,把日志输出弄细了些方便出现异常的时候可以回溯定位问题,并增加了≥16TB盘的逻辑:超过16T的盘应该按8192的块大小分区,默认块大小4096最大只能支持到16T分区,超过16TB的那部分空白空间用不上

目前在2008R2、2012R2、2016、2019、2022中文版测试挂20块32000GB的盘进行极限测试是可以顺利初始化的

代码语言:javascript
代码运行次数:0
运行
复制
#ps1

$Global:UserMount = 'E'
$Global:TotalDataDisk = 0
$disks =Get-WmiObject Win32_DiskDrive |   Where {$_.Partitions -eq 0} | select DeviceID,Size|findstr /i PHYSICALDRIVE | ForEach-Object { ($_.split("E ")[1].PadLeft(2)) + ',' + ($_.Split(" ")[-1]) }|sort

function SetAllDiskOnline {
    Write-Output 'SetAllDiskOnline' >> C:\disk_init.txt
    $DiskList = 'list disk' | diskpart.exe
    Write-Output $DiskList >> C:\disk_init.txt
    foreach ($Disk in $DiskList) {
        if ($Disk -match 'Disk\s+(?<DiskIdx>\d+) | \u78c1\u76d8\s+(?<DiskIdx>\d+)') {
            $DiskIdx = $Matches.DiskIdx
            Write-Output "Setting disk $DiskIdx online" >> C:\disk_init.txt
            if ([int]$DiskIdx -ge 1) {
                $Global:TotalDataDisk = $Global:TotalDataDisk + 1
                $DiskOps = @"
select disk $DiskIdx
online disk
san policy=OnlineAll
exit
"@
                $DiskOps | diskpart.exe | Out-Null
                Start-Sleep -Seconds 0.1
            }
        }
    }
}

function CheckShouldInit {
    Write-Output 'CheckShouldInit' >> C:\disk_init.txt
    $VolumeList = 'list volume' | diskpart.exe
    $TotalVolume = 0
    foreach ($Volume in $VolumeList) {
        if ($Volume -match 'Volume\s+(?<VolumeIdx>\d+) | \u5377\s+(?<VolumeIdx>\d+)') {
            $TotalVolume = $TotalVolume + 1
        }
    }
    if ($TotalVolume -gt 3) {
        Write-Output 'Not a new instance, exit' >> C:\disk_init.txt
        exit
    }
    Write-Output 'New instance to be initilized' >> C:\disk_init.txt
}

function InitDataDisk {
    Write-Output 'InitDataDisk' >> C:\disk_init.txt
    $DiskList = 'list disk' | diskpart.exe
    $DriverLetterList = [char[]](69..90)
    $Idx = 0
    foreach ($Disk in $DiskList) {
        if ($Disk -match 'Disk\s+(?<DiskIdx>\d+)\s+(Online|Offline)\s+(?<Size>\d+)\s+GB\s+(?<Free>\d+)|\u78c1\u76d8\s+(?<DiskIdx>\d+)\s+(\u8054\u673a|\u8131\u673a)\s+(?<Size>\d+)\s+GB\s+(?<Free>\d+)|Disk\s+(?<DiskIdx>\d+)\s+(Online|Offline)\s+(?<Size>\d+)\s+TB\s+(?<Free>\d+)|\u78c1\u76d8\s+(?<DiskIdx>\d+)\s+(\u8054\u673a|\u8131\u673a)\s+(?<Size>\d+)\s+TB\s+(?<Free>\d+)') {
            $DiskIdx = $Matches.DiskIdx
            if ($DiskIdx -ge 1) {
                $DriverLetter = $DriverLetterList[$Idx]
                if ($Global:TotalDataDisk -le 1) {
                    $DriverLetter = $Global:UserMount
                }




				$disktype="mbr"
				if(([Math]::Round($disks[$DiskIdx-1].split(",")[1]/1024/1024/1024)) -gt 2048)
				{
				$disktype="gpt"
				}

				$unit=4096
				if(([Math]::Round($disks[$DiskIdx-1].split(",")[1]/1024/1024/1024)) -gt 16384)
				{
				$unit=8192
				}

                $DiskOps = @"
SELECT DISK $DiskIdx
online disk NOERR
attr disk clear readonly NOERR
convert $disktype NOERR
CREATE PARTITION PRIMARY NOERR
format fs=ntfs unit=$unit quick NOERR
ASSIGN LETTER=$DriverLetter
san policy=onlineall
EXIT
"@

                Write-Output "Start initializing disk $DiskIdx" >> C:\disk_init.txt
                $DiskOps | diskpart.exe >> C:\disk_init.txt

                Start-Sleep -Seconds 0.1

                Write-Output "DataDisk $DiskIdx is assigned to driver: $DriverLetter" >> C:\disk_init.txt
                $Idx = $Idx + 1
            }
        }
    }
}

Write-Output 'Initializing data disks' >> C:\disk_init.txt

SetAllDiskOnline

CheckShouldInit
InitDataDisk

下面是对这段 PowerShell 脚本的通俗解析与重点评审,按执行流程、关键逻辑、正则/本地化细节以及风险与改进建议逐步说明。

一、脚本总体目的

  • 该脚本用于在 Windows(常见于云主机或新装系统场景)上自动初始化“数据盘”(通常指除 0 号系统盘之外的磁盘)。
  • 主要动作包括:将所有数据盘联机、根据容量选择分区表类型(>2TB 用 GPT,否则 MBR)、整盘创建单个主分区、用 NTFS 快速格式化、分配盘符(默认从 E 开始)、以及记录日志到 C:\disk_init.txt。
  • 对 diskpart 输出做了中英文双语(英文 Disk/Volume;中文 磁盘/卷/联机/脱机)的兼容匹配。

二、全局变量与磁盘列表

  • $Global:UserMount = 'E':默认优先使用的盘符为 E(当只有 1 块数据盘时,会强制使用 E)。
  • $Global:TotalDataDisk = 0:统计数据盘数量(不包括 0 号系统盘)。
  • $disks:通过 WMI 获取“当前没有分区”的物理磁盘(Partitions -eq 0),并抽取 DeviceID、Size 后混合外部 findstr 与字符串切分,最终得到一个字符串数组,每个元素形如“某个前缀,磁盘字节大小”。后续仅用它的“逗号后半部分(字节数)”来判定磁盘大小,从而决定使用 MBR 还是 GPT,以及 NTFS 的 allocation unit 大小。

重要提醒:

  • 这里混合使用 Get-WmiObject + findstr + split 的方式比较脆弱,依赖输出格式;且最后还对字符串做了 sort,可能导致数组顺序与真实“磁盘序号”不一致,从而把别的磁盘的大小“错配”给当前磁盘,用错 MBR/GPT 或 allocation unit(见后文“潜在问题”)。

三、SetAllDiskOnline 函数(使所有数据盘联机)

  • 先将 “SetAllDiskOnline” 与 diskpart 的 “list disk” 输出写入 C:\disk_init.txt。
  • 遍历 “list disk” 的每一行,使用正则匹配两种语言格式:
    • 英文:Disk\s+(?\d+)
    • 中文:\u78c1\u76d8\s+(?\d+)(即“磁盘 <编号>”)
  • 对匹配到的磁盘号 DiskIdx,如果 >= 1(跳过 0 号系统盘),则:
    • $Global:TotalDataDisk 自增(统计数据盘数量)。
    • 生成一段 diskpart 脚本:select disk N -> online disk -> san policy=OnlineAll -> exit,并喂给 diskpart 执行。
    • Start-Sleep -Seconds 0.1(注意:-Seconds 参数是整数,这里 0.1 会被当成 0,实际上没有等待;若想 100ms,应使用 -Milliseconds 100)。

作用与意义:

  • 确保所有数据盘都被联机,且将 SAN 策略设为 OnlineAll(在某些服务器默认策略下,新盘会离线或只读,设为 OnlineAll 有利于自动化挂载)。

四、CheckShouldInit 函数(判断是否需要初始化)

  • 写日志后调用 diskpart 的 “list volume”,逐行用正则匹配卷:
    • 英文:Volume\s+(?\d+)
    • 中文:\u5377\s+(?\d+)(即“卷 <编号>”)
  • 统计到的卷数若 > 3,则写日志“Not a new instance, exit”并 exit 整个脚本;否则写日志“New instance to be initilized”继续。
  • 本质是“启发式地”判断是否是“新机器/新环境”。通常新系统有 2~3 个卷(系统/保留/恢复),若超过 3 则认为不是“初始场景”,避免误格式化数据盘。

注意:

  • 这个判断是启发式的,不一定可靠(比如某些镜像自带更多卷,或系统分区布局不同)。

五、InitDataDisk 函数(真正初始化数据盘)

  • 写日志,调用 “list disk” 得到磁盘清单。
  • 生成字母数组 [E..Z],用于多盘场景的顺序分配。
  • 遍历 “list disk” 输出的每一行,用一个较长的正则同时兼容:
    • 英文/中文
    • 联机/脱机(Online/Offline 或 联机/脱机)
    • 容量单位为 GB 或 TB
    • 从中提取 DiskIdx(磁盘号)、Size(容量数字)、Free(空闲)
  • 对匹配到的每个磁盘,若 DiskIdx >= 1(跳过系统盘 0)则:
    1. 计算目标盘符:
      • 如果 $Global:TotalDataDisk -le 1,则强制使用 $Global:UserMount(E);
      • 否则按 [E..Z] 顺序分配,$Idx 每处理一块自增。
    2. 决定分区表类型 disktype:
      • 用 $disks[$DiskIdx-1] 里“逗号右侧”的字节数换算 GiB,Round 后若 > 2048(约 2TB)则用 GPT,否则 MBR。
    3. 决定 NTFS allocation unit(簇大小)unit:
      • 默认 4096(4KB),如果 > 16384 GiB(16TB)则设为 8192(8KB)。
    4. 拼装 diskpart 脚本并执行:
      • SELECT DISK N
      • online disk NOERR
      • attr disk clear readonly NOERR
      • convert mbr/gpt NOERR
      • CREATE PARTITION PRIMARY NOERR
      • format fs=ntfs unit=(4K/8K) quick NOERR
      • ASSIGN LETTER=目标盘符
      • san policy=onlineall
      • EXIT
      • 把 diskpart 的输出也追加到 C:\disk_init.txt
    5. 记录“DataDisk N is assigned to driver: X”日志

六、正则与本地化细节

  • 使用了 \u 编码来兼容中文环境:
    • \u78c1\u76d8 = 磁盘
    • \u5377 = 卷
    • \u8054\u673a = 联机
    • \u8131\u673a = 脱机
  • 通过 alternation(|)在同一条正则里兼容英文与中文输出。
  • 注意正则里同名命名组 DiskIdx 在两个分支都存在,.NET 正则可用,匹配成功的分支会给出 DiskIdx 值。

七、潜在问题与风险提示

  • 极高的“数据抹除风险”:InitDataDisk 中的 convert、create partition、format 操作会清空磁盘。虽然 CheckShouldInit 做了卷数量判断,但这只是启发式,不能保证 100% 安全。若误判“新环境”,将导致数据丢失。
  • $disks 的构造与排序存在错配风险:
    • 通过 findstr + split 的文本管道很脆弱,且尾部用 sort 对字符串排序,未保证“数组下标 = 磁盘号-1”的严格对应。一旦错配,某些磁盘会被错误地判为 >2TB 或 <=2TB,从而用错 MBR/GPT 或簇大小。
    • 建议直接用 Get-Disk(或 Get-CimInstance/Win32_DiskDrive)按 Number 精确取对应磁盘的 Size,而不是用一个全局数组按位置去对号入座。
  • Start-Sleep -Seconds 0.1 实际等于 0 秒(参数是整数),如果确需短暂等待,应使用 Start-Sleep -Milliseconds 100。
  • 盘符分配冲突:
    • ASSIGN LETTER=E 若 E 已被占用(例如光驱),命令会失败。当前没有为 ASSIGN 增加 NOERR,可能在个别系统上导致脚本中断或行为异常。
  • 依赖 diskpart 输出的“语言与格式”:
    • 虽然用正则兼容了中英文,但依旧对输出格式/空格位置比较敏感,未来系统版本/区域设置可能变化导致解析失败。
  • WMI 命令与兼容性:
    • Get-WmiObject 在新版本 PowerShell(Core)中已被建议使用 Get-CimInstance 替代。
  • 簇大小策略值得再评估:
    • 当前仅在 >16TB 才改为 8KB;很多数据库/大文件场景会推荐 64KB。是否用 8KB 要结合具体负载(例如 SQL Server 官方常推荐 64KB)。

八、可改进建议(生产化思路)

  • 用原生存储 Cmdlet 代替解析 diskpart 文本:
    • Get-Disk | Where-Object Number -ge 1 | Where-Object PartitionStyle -eq 'RAW'
    • Initialize-Disk -Number N -PartitionStyle GPT/MBR
    • New-Partition -DiskNumber N -UseMaximumSize -DriveLetter X
    • Format-Volume -DriveLetter X -FileSystem NTFS -AllocationUnitSize 4096/65536 -Confirm:$false
    • 这样可直接基于磁盘“编号/属性”操作,不再依赖文本解析与跨语言正则。
  • 更稳妥的“新环境判定”:
    • 例如仅对 PartitionStyle 为 RAW 的磁盘执行初始化;或引入“幂等保护文件/注册表标记”;或明确通过外部信号(参数/标签)来控制是否初始化。
  • 更合理的簇大小策略
    • 默认 4KB(4096)适合通用场景;数据库/日志/大文件场景通常推荐 64KB(65536)。8KB 较少见,除非你有明确的工作负载依据。
    • 建议把簇大小参数化,让调用者按实际负载传入(例如 4K 或 64K)。
  • 盘符分配的安全性
    • 动态查找空闲盘符(从 E 开始到 Z),避开已占用盘符与光驱盘符,自动回退到下一个可用字母,避免 ASSIGN 失败。
  • 幂等与边界保护
    • 仅对 PartitionStyle 为 RAW 的新盘做初始化(不对已有分区/数据的磁盘动刀)。
    • 增加“标记文件/注册表”或对每块盘打标签(卷标/磁盘备注),下次执行先检测已处理标记,避免重复格式化。
    • 永远跳过 0 号系统盘(你的脚本已有保护)。
  • 日志与错误处理
    • 用 try/catch 捕捉失败并写入日志;失败后对该盘停止后续操作,避免半初始化状态。
    • 重要步骤(联机、清只读、初始化、分区、格式化、分配盘符)都记录到同一日志文件中。
  • 延时与重试
    • 某些环境联机/初始化后对象化信息冒泡有延迟,建议在关键步骤后短暂等待(如 100–300ms),或循环等待资源就绪(查询 Get-Partition/Get-Volume)。
  • 本地化与兼容性
    • 避免解析 diskpart 的中英文文本输出,统一用 Storage 模块(Get-Disk/Initialize-Disk/New-Partition/Format-Volume 等)进行对象化操作,从而规避区域设置差异带来的解析脆弱点。
    • 如果必须设置 SAN 策略,可单独用一次性 diskpart 命令 san policy=OnlineAll,但注意其为系统级策略,需评估影响。
  • 权限与运行环境
    • 需以管理员权限运行;优先使用 Windows PowerShell 5.1 或 PowerShell 7 + Windows 兼容会话。模块依赖:Storage。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档