Skip to content

antony@notes:~/virtualization$ cat "2023-Windows-VMware-IaC-開發及使用手冊.md"

2023 Windows & VMware IaC 開發及使用手冊

2023-04-24· virtualization

2023 Windows & VMware IaC 開發及使用手冊

緣由

       要手動部屬多台虛擬主機時,有高機率造成 Human Error ,會產生大量時間成本

       虛擬化技術中,一個資料夾代表一台電腦,要設定每一台虛擬主機的 hostname, IP 位址, 名稱解析檔…等,此時容易發生 Human Error。

       當每一台虛擬主機手動做好以上設定時,要將每一台虛擬主機先壓縮,此時必須坐在電腦前面,手動將每一台虛擬主機都壓縮,壓縮完後,再手動將每一個壓縮檔,派送到對應的實體 Windows 電腦,再手動將每一台虛擬主機的壓縮檔解壓縮,最後再進到每一台虛擬主機的目錄,將虛擬主機啟動。

       以上動作,如果沒有事先規劃好,壓縮檔的檔名,要放在哪個目錄,哪一台虛擬機要派送到哪一台實體 Windows 電腦,要解壓縮到哪一個目錄,當叢集需要用到的 虛擬主機數量越大時,會造成管理上的不方便。

       當以上動作完成,將已派送到對應 Windows 主機裡的虛擬主機都啟動後,又發現與當初規劃的規格不符,此時又要跳回第一步,會耗用大量時間成本。

認識 IaC

伺服器環境的手動配置給 IT 專業人員帶來了一些挑戰,例如,由於不斷增加的複雜性和對 IT 基礎架構的相關需求。

Infrastructure as Code,中文翻為 “基礎架構程式碼” ,提供了一種替代方法,將基礎架構的提供、配置與管理方式自動化。

Infrastructure 以實體比喻的話,就是機房裡的 Server

Iac 分為兩種方法,分別是 Declarative (聲明式) 和 Imperative (宣告式)

  1. 聲明式主要說明基礎系統架構要完成甚麼目標、需要的資源或任何狀態
  2. 宣告式定義了實現基礎系統架構所需的特定命令,並以正確的順序執行這些命令。

執行它的好處不僅可以降低 Human Error縮短建置系統的時間,還可以確保每次都建立出一模一樣的基礎架構

舉例,在 CNT.2023.v4.6 的套件包裡面的 docker.bat, k8s.bat, dt.bat …等批次檔案,都能快速建立起,對應的基礎架構。

註解:IaC 技術幫我們從落地雲主機自動產生以及複製虛擬主機,並派送至多台不同的實體 Windows 10 主機

參考文章連結 :


Windows & VMware IaC 平台設計

平台目標

自動化建置與管理分散型叢集系統基礎架構

以 Kubernetes 架構範例 :

  • 一台 Master 主機
  • 三台 Worker 主機

以 Hadoop 架構範例 :

  • 一台 Admin 機器
  • 兩台 Master 機器
    • M1 跑 Name Node, Secondary Name Node
    • M2 跑 Resource Manager, Jobhistory Server
  • 三台 Worker 機器
    • 每一台機器都會跑 Data Node, Node Manager

重要操作步驟

  1. 設計叢集架構 (mactohost)
  2. 產生虛擬電腦資料夾
  3. 壓縮虛擬電腦資料夾
  4. 派送虛擬電腦資料夾
  5. 解壓縮所有虛擬主機壓縮檔
  6. 啟動與檢視所有虛擬主機
  7. 關閉所有虛擬主機
  8. 備份與還原所有虛擬主機
  9. 清除所有虛擬主機

實體電腦硬體規格

  • 多台實體 Windows 10 電腦
  • 記憶體至少 24 G
  • CPU: 4 C / 2 HT 以上
  • 須為靜態 IPv4 的 IP 位址
  • 每台實體 Windows 10 電腦會分別跑一台或多台不同的虛擬電腦

虛擬電腦硬體規格

  • Linux 作業系統
  • 記憶體 : 依建置叢集需求設定
  • CPU : 依建置叢集需求設定
  • 網路模式 : bridge

虛擬電腦網路設定

根據叢集的大小來規劃 IP 位址、 Subnetmask 以及 Default Gateway


開始建置 C1 Cluster (1M3W)

建置叢集系統前準備

  1. 關閉防火牆 :::spoiler 步驟一 : 打開"檔案總管",滑鼠右鍵點選"網路",選擇 “內容” ::: :::spoiler 步驟二 : 點選 “Windows Defender 防火牆” ::: :::spoiler 步驟三 : 點選 “開啟或關閉 Windows Defender 防火牆” ::: :::spoiler 步驟四 : 在私人網路 和 公用網路 下,兩個都點選 “關閉 Windows Defender 防火牆(不建議)",再按確定 :::

  2. 確認實體 Windows 10 主機之間可用 SSH 連接 測試命令 :

    > ssh bigred@<要派送的實體 Windows 10 電腦 的 IP 位址>

    無法連線,請用滑鼠右鍵點選 Windows Powershell 圖示,選擇"以系統管理員身分執行”,並執行以下命令

    • 檢查 OpenSSH 目前的狀態
      PS > Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'
      如果 OpenSSH Server 沒有安裝,命令應返回以下輸出 :
      Name  : OpenSSH.Client~~~~0.0.1.0
      State : Installed
      
      Name  : OpenSSH.Server~~~~0.0.1.0
      State : NotPresent
    • 安裝 OpenSSH Server 命令 :
      PS > Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
      螢幕輸出 :
      Path          :
      Online        : True
      RestartNeeded : False
    • 啟動 sshd 服務命令
      PS > Start-Service sshd
    • 將 sshd 服務設為開機自動啟動
      PS > Set-Service -Name sshd -StartupType 'Automatic'
    • 連線要派送虛擬主機壓縮檔的實體 Windows 10 電腦 的 IP 位址 測試
      > ssh bigred@<要派送的實體 Windows 10 電腦 的 IP 位址>

    [註] Install OpenSSH Server on Windows 10 官網連結

  3. 將連接 Windows 實體主機 使用者的密碼存入 passwd 檔案

    > notepad %USERPROFILE%\CNT.2023.v4.6\C1-1M3W\passwd
  4. 將連接 模板 虛擬主機 使用者的密碼存入 passwd_vm 檔案

    > notepad %USERPROFILE%\CNT.2023.v4.6\C1-1M3W\passwd_vm
  5. 請執行 VMware Player 這軟體以下設定 步驟一 : Player > File > Preferences... 步驟二 : In the “Software updates” group > uncheck the “Check for software components as needed” option.

  6. 確認被遠端控制的實體 Windows 主機,開放讓 psexec 遠端連接權限 用系統管理員身份打開 CMD,並執行以下命令

    reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\system /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 /f

    [註] 1. 機碼修改後,需重啟電腦        2. 如有安裝 F-Secure,請關閉 F-Secure 防火牆 參考文章連結 : PsTools 於 Windows 10 的環境設定

  7. 確認 Windows 主機上當前的使用者家目錄下 ssh 的私鑰 %USERPROFILE%\.ssh\id_rsa 權限是否太開放, :::spoiler 示意圖
    ::: 如果在群組或其他使用者這個欄位下,有其他人,那麼請執行以下命令 :

    > cd %USERPROFILE%\CNT.2023.v4.6\C1-1M3W && sshsetup.bat

    螢幕輸出 :

    SSH key Setup ok !

    :::spoiler 示意圖 :::

1. 設計 c1 叢集架構

設定 c1 叢集架構檔 - mactohost

在 Windows 中,打開 CMD ,執行以下命令

切換至工作目錄

> cd CNT.2023.v4.6\C1-3M3W

編輯 c1mactohost

> notepad c1mactohost

檔案內容 :

# Default Cluster
00:00 x1 none
00:01 x2 VMware
00:02 x3 VMware
00:03 a1 admin
00:04 m1 master
00:05 m2 master
00:06 m3 master
00:07 w1 worker
00:08 w2 worker
00:09 w3 worker
# K8S c1 Cluster ALP.K8S
10:f0 c1a1 admin
10:f1 c1m1 master c1m1.241.zip c1/alp.c1m1 120.96.143.170
10:f2 c1m2 master
10:f3 c1m3 master
10:f4 c1e1 etcd
10:f5 c1w1 worker c1w1.245.zip c1/alp.c1w1 120.96.143.170
10:f6 c1w2 worker c1w2.246.zip c1/alp.c1w2 120.96.143.171
10:f7 c1w3 worker c1w3.247.zip c1/alp.c1w3 120.96.143.171
10:f8 c1w4 worker
10:f9 c1w5 worker

欄位說明 (用 空格 做切割) :

  • 先單獨看第 12 行內容,
    # K8S c1 Cluster ALP.K8S
    • 第三欄 c1 會與 CNT.2023.V4.6\C1-1A3M\c1mactohost 這個檔案的前兩個字 c1 比較,所以不能亂設定
    • 第五欄 ALP.K8S 設定 模板機的目錄名稱
      • 模板機必須放在 %USERPROFILE%\CNT.2023.v4.6\目錄下
  • 第一欄為 設定 VM 主機 的 Mac Address 最後兩個位元 (byte)
    • 用冒號分隔的格式 : 前半部是 c1 = 10, c2 = 20… 依此類推 後半部是 VM 的 IP 位址尾數換算為 16 進位
    • 舉例 : 假設現在要設定的是 c1 叢集裡面 IP 位址尾數 .210 的 VM, 依照上述規則,得到的答案 : 10:d2 :::danger Note:     1. 16 進位中的英文需設為小寫     2. 不能設為 : 10:xx ,會導致虛擬機器的 /etc/hosts 無法正常解析 !     3. 在此檔案中的任何一台 VM 的 Mac Address 不可重複
      :::
  • 第二欄為 設定 VM 主機名稱
    • 格式 : 叢集名稱 + mactohost 第三欄位的第一個字 + 序號(從 1 開始)
  • 第三欄為 各 VM 的角色扮演
  • 第四欄為 VM 目錄壓縮檔名
    • 格式 : VM 主機名稱 + VM IP 位址的尾數 + 壓縮檔的附檔名 (.zip)
  • 第五欄為 VM 存放的目錄名稱
    • 格式 : 叢集名稱/us.主機名稱 (us = Ubuntu Server 的縮寫)
    • 格式 : 叢集名稱/alp.主機名稱 (alp = Alpine Linux 的縮寫)
  • 第六欄為 VM 要分配到哪一台實體電腦的 IP 位址

2. 建立 c1 叢集所有虛擬主機

create.bat 程式概述 : 在落地雲主機的 C:\Users\bigred\CNT.2023.v4.6\C1-1M3W 目錄下,做出 c1 的資料夾,並在 c1 目錄下建立 4 台虛擬機 [重要] 根據實體電腦的記憶體來修改 vmcreate.bat 中 RSMMAX 變數直的大小

:::spoiler 完整程式內容

@echo off

REM 設定 Cluster Name
for /f "tokens=1" %%y in ('dir /B ^| findstr "mactohost"') do (
  set mactohost=%%y
)

for /f "tokens=3 delims= " %%z in ('type %mactohost% ^| more +11 ^| findstr "#"') do (
  set CN=%%z
)

REM 判斷 Cluster Name 有沒有設定錯誤
if NOT %mactohost:~0,2% == %CN% (
  echo "Cluster Name Error !"
  GOTO:eof
)

if exist %CN%\ (
  echo "%CN% Folder existed !"
  GOTO:eof
)

REM 設定 模板機所在目錄名稱 和 模板機的 vmx 檔案位置 變數
for /f "skip=1 tokens=5 delims= " %%a in ('type %mactohost% ^| findstr Cluster') do (
  set vmfolder=%%a
  for /f "tokens=1" %%g in ('dir /S /B ..\%%a\ ^| findstr vmx$') do (
    set vmx=%%g
  )
)

REM 將 c*mactohost 中 實體 Windows 主機的 IP 匯入 ip.txt 檔案中
if exist ip.txt (del ip.txt)
for /f "skip=12 tokens=6 delims= " %%y in (%mactohost%) do (
  echo %%y >> ip.txt
)

REM 判斷要派送的主機 22 port 有沒有開, psexec 功能是否正常
setlocal EnableDelayedExpansion
for /f "tokens=1 delims= " %%h in ('type ip.txt ^| sort.exe /unique') do (
  for /f "tokens=1" %%p in ('powershell -command "& {(New-Object System.Net.Sockets.TcpClient).ConnectAsync('%%h', 22).Wait(100)}"') do (
    if %%p == False echo "%%h SSH Error" && GOTO:eof
  )

  REM 設定本機 IP 變數
  for /f "delims=[] tokens=2" %%e in ('ping -4 -n 1 %ComputerName% ^| findstr [') do (
    set HostIP=%%e
  )
  if not defined HostIP "echo !HostIP! should manual configuration in %~nx0"
  if not !HostIP! == %%h (
    psexec -accepteula -nobanner \\%%h -s "hostname" > nul 2> nul
  )
  if !ERRORLEVEL! NEQ 0  (
    echo "%%h psexec Error" && GOTO:eof
  )
)
del ip.txt 2> nul > nul

REM 判斷 VM 使用的 IP 目前有無被占用
for /f "skip=12 tokens=4,6 delims= " %%a in (%mactohost%) do ( 
  for /f "tokens=1-3 delims=." %%x in ('echo %%b') do (
    for /f "tokens=2 delims=." %%i in ('echo %%a') do (
      ping -n 1 %%x.%%y.%%z.%%i | find "TTL" 2> nul >nul
        if !ERRORLEVEL! EQU 0 echo "%%x.%%y.%%z.%%i is already being used." && GOTO:eof
      )
    )
  )
)

REM 判斷 10 進位轉 16 進位有沒有算錯
for /f "tokens=1,2 delims=." %%k in ('type %mactohost% ^| findstr zip') do (
  for /f "tokens=2 delims=:" %%s in ("%%k") do (
    for /f "tokens=1" %%o in ("%%s") do (
      set "h=%%o"
      set "DEC=%%l"
      cmd /C exit !DEC!
      set "HEX=!=ExitCode!"
      for /F "tokens=* delims=0" %%Z in ("!HEX!") do (
        if /I !DEC! LSS 16 (
          set "HEX=0%%Z"
          for /F "delims=" %%r in ('powershell -command " '!HEX!'.ToLower() "') do (
            set "SHEX=%%r"
            if NOT "!h!" EQU "!SHEX!" echo The hexadecimal representation of !DEC! is !SHEX!, not !h!. && GOTO:eof
          )
        ) else (
          set "HEX=%%Z"
          for /F "delims=" %%r in ('powershell -command " '!HEX!'.ToLower() "') do (
            set "SHEX=%%r"
            if NOT "!h!" EQU "!SHEX!" echo The hexadecimal representation of !DEC! is !SHEX!, not !h!. && GOTO:eof
          )
        )
      )
    )
  )
)

REM 設定 VM 可用記憶體範圍
powershell -Command "(Get-CimInstance Win32_PhysicalMemory | Measure-Object -Property capacity -Sum).sum /8Mb" > max.txt
powershell -Command "(Get-CimInstance Win32_PhysicalMemory | Measure-Object -Property capacity -Sum).sum /8Mb" > min.txt

set /P MAX=<max.txt
set /P MIN=<min.txt

for /f "tokens=2 delims==" %%a in ('type %vmx% ^| findstr memsize') do (
  set mem=%%a
)
set RSMMAX='memsize = \"%mem:~2,-1%\"', 'memsize = \"%MAX%\"'
set RSMMIN='memsize = \"%mem:~2,-1%\"', 'memsize = \"%MIN%\"'

set RSADR1='ethernet0.generatedAddressOffset = \"0\"'
set RSADR2='ethernet0.addressType = \"generated\"' , 'ethernet0.addressType = \"static\"'
set RSTYPE='ethernet0.connectionType = \"nat\"' , 'ethernet0.connectionType = \"bridge\"'

for %%i in (vmfolder vmx mem) do (
  if not defined %%i (
    echo %%i not defined && GOTO:eof
  )
)

REM 上載 mactohosts 至 模板 虛擬主機
"C:\Program Files (x86)\VMware\VMware Player\vmrun" start "%vmx%" > nul
timeout /t 60 > nul

"C:\Program Files (x86)\VMware\VMware Player\vmrun" getGuestIPAddress "%vmx%" > admip.txt
set /P admip=<admip.txt

echo y | pscp -pwfile "passwd_vm" ".\%CN%mactohost" bigred@%admip%:/home/bigred/bin/mactohost 2> nul > nul
if %ERRORLEVEL% EQU 0 echo "%CN%mactohost scp ok"

echo y | pscp -pwfile "passwd_vm" bigred@%admip%:/home/bigred/.ssh/* %USERPROFILE%\.ssh\ 2> nul > nul
if %ERRORLEVEL% EQU 0 echo "%admip% .ssh/* scp ok"

del %USERPROFILE%\.ssh\known_hosts 2> nul
del %USERPROFILE%\.ssh\known_hosts.old 2> nul

type %vmx% | findstr Ubuntu > nul 2> nul
if %ERRORLEVEL% NEQ 0 (
  ssh -o "ConnectTimeout=5" -o "StrictHostKeyChecking=no" bigred@%admip% "sudo apk update; sudo apk upgrade" > nul 2>nul
  if !ERRORLEVEL! EQU 0 echo "System upgrade ok"
) else (
  ssh -o "ConnectTimeout=5" -o "StrictHostKeyChecking=no" bigred@%admip% "sudo apt update; sudo apt -y upgrade; sudo apt -y autoremove" > nul 2>nul
  if !ERRORLEVEL! EQU 0 echo "System upgrade ok"
)

"C:\Program Files (x86)\VMware\VMware Player\vmrun" stop "%vmx%" > nul
timeout /t 60 > nul

REM 設定模板機 displayName 變數
for /f "tokens=2 delims==" %%a in ('type %vmx% ^| findstr displayName') do (
  set odn=%%a
)

REM 設定模板機 mac address 變數
for /f "tokens=2 delims==" %%a in ('type %vmx% ^| findstr "ethernet0.generatedAddress" ^| findstr ":"') do (
  set vmac=%%a
)

for %%j in (odn vmac) do (
  if not defined %%j (
    echo %%j not defined && GOTO:eof
  )
)

REM 建立 Cluster 的所有 VM
if exist %mactohost% (
  if not exist %CN%\ (
    mkdir %CN% > nul
    if %ERRORLEVEL% EQU 0 (
      for /f "tokens=1-6 delims= " %%a in ('findstr zip %mactohost%') do (
        set x=%%~nxe
        set RS='displayName = \"%odn:~2,-1%\"', 'displayName = \"!x!\"'
        set y=%%a
        set RSMAC='ethernet0.generatedAddress = \"%vmac:~2,-1%\"' , 'ethernet0.address = \"00:50:56:ab:!y!\"'

        mkdir %CN%\!x! > nul
        xcopy /E /Y ..\%vmfolder% %CN%\!x!\ > nul

        for /f "tokens=1" %%p in ('dir %CN%\!x! /B /S ^| findstr vmx$') do set nvmx=%%p
        powershell -Command "(gc !nvmx!) -replace "!RS!" -replace "!RSMAC!" -replace "%RSMMAX%" -replace "%RSTYPE%" -replace "%RSADR1%" -replace "%RSADR2%"  | Out-File -Encoding "UTF8" vm.temp"

        del /Q !nvmx!
        copy /Y vm.temp !nvmx! > nul
        if !ERRORLEVEL! EQU 0 echo "%CN%\!x!\ (%MAX%) ok"
      )
      del *.txt > nul
      del vm.temp > nul
    )
  )
)

:::

執行建立 c1 叢集命令

在執行命令前,請先確定是否已完成 叢集系統準備

在 Windows 工作目錄 CNT.2023.v4.6\c1-1M3W 下,執行程式

> vmcreate.bat

螢幕輸出

"c1mactohost scp ok"
"192.168.61.131 .ssh/* scp ok"
"System upgrade ok"
"c1\alp.c1m1\ (4096) ok"
"c1\alp.c1w1\ (4096) ok"
"c1\alp.c1w2\ (4096) ok"
"c1\alp.c1w3\ (4096) ok"

[註] 如遇到需要輸入密碼,請參考以下連結 :        Windows SSH: Permissions for ‘private-key’ are too open

3. 壓縮 c1 叢集所有虛擬主機

zip.bat 程式概述 : 此程式使用 7z 作為虛擬主機目錄的壓縮命令

:::spoiler 完整程式內容

@echo off

REM 設定 Cluster Name
for /f "tokens=1" %%y in ('dir /B ^| findstr "mactohost"') do (
  set CN=%%y
)

REM 在 mactohost 中抓取壓縮時需要的字串
if exist %CN:~0,2%zip.txt (del %CN:~0,2%zip.txt > nul)
for /f "tokens=3,* delims= " %%a in (%CN:~0,2%mactohost) do echo %%b | findstr zip >> %CN:~0,2%zip.txt

REM 透過 For 迴圈,逐行代變數壓縮 VM 所在的資料夾
setlocal EnableDelayedExpansion
for /f "tokens=1,2 delims= " %%a in (%CN:~0,2%zip.txt) do (
  7z\7z -mx1 a %CN:~0,2%\%%a %%b > nul
  if !ERRORLEVEL! EQU 0 (echo "%%a ok")
)
del %CN:~0,2%zip.txt > nul

:::

執行壓縮程式

> vmzip.bat

螢幕輸出

"c1m1.141.zip ok"
"c1w1.145.zip ok"
"c1w2.146.zip ok"
"c1w3.147.zip ok"

4. 派送 c1 叢集所有虛擬主機

scp.bat 概述 : 使用 pscp.exe 命令,將 c1 叢集每一台虛擬主機的壓縮檔派送到 mactohost 中設定對應的各台實體 Windows 主機

:::spoiler 完整程式內容

@echo off

REM 判斷叢集名稱
for /f "tokens=1" %%y in ('dir /B ^| findstr "mactohost"') do (
  set CN=%%y
)

REM 設定本機 IP 變數
for /f "delims=[] tokens=2" %%e in ('ping -4 -n 1 %ComputerName% ^| findstr [') do (
  set HostIP=%%e
)

REM 抓取在 d*mactohost 中,每一台虛擬主機的相關資訊,並將資料逐行匯入 d*scp.txt
if exist %CN:~0,2%scp.txt (del %CN:~0,2%scp.txt > nul)
for /f "tokens=1* delims= " %%a in (%CN:~0,2%mactohost) do echo %%b | findstr zip >> %CN:~0,2%scp.txt

REM 將每一台虛擬主機的壓縮檔透過 pscp 命令,派送至對應的實體 Windows 主機的使用者家目錄
setlocal EnableDelayedExpansion
for /f "tokens=3,5 delims= " %%a in (%CN:~0,2%scp.txt) do (
  if %%b == %HostIP% (
    xcopy /Y %CN:~0,2%\%%a %USERPROFILE%\ > nul
    if !ERRORLEVEL! EQU 0 (
      echo "%%a to %%b ok"
    ) else (
      echo "%%a to %%b failed"
    )
  ) else (
    echo y | pscp -scp -q -pwfile passwd "%CN:~0,2%\%%a" %USERNAME%@%%b:"%%a" 2> nul > nul
    if !ERRORLEVEL! EQU 0 (
      echo "%%a to %%b ok"
    ) else (
      echo "%%a to %%b failed"
    )
  )
)
del %CN:~0,2%scp.txt > nul

:::

執行程式

> vmscp.bat

螢幕輸出

"c1m1.141.zip to 120.96.143.152 ok"
"c1w1.145.zip to 120.96.143.152 ok"
"c1w2.146.zip to 120.96.143.153 ok"
"c1w3.147.zip to 120.96.143.153 ok"

5. 解壓縮所有虛擬主機壓縮檔至落地雲主機

unzip.bat 程式概述 : 本程式在本機使用 7z 將 虛擬主機的壓縮檔解壓縮,遠端的主機使用 Windows Powershell 的 Expand-Archive 命令

:::spoiler 完整程式內容

@echo off

REM 判斷叢集名稱
for /f "tokens=1" %%y in ('dir /B ^| findstr "mactohost"') do (
  set CN=%%y
)

REM 設定本機 IP 變數
for /f "delims=[] tokens=2" %%a in ('ping -4 -n 1 %ComputerName% ^| findstr [') do (
  set HostIP=%%a
)

REM 解壓縮在本地或遠端的虛擬主機壓縮檔
for /f "skip=12 tokens=4-6 delims= " %%a in (%CN:~0,2%mactohost) do (
  if %%c == %HostIP% (
    setlocal EnableDelayedExpansion
    "%ProgramFiles%\7-Zip\7z" "x" "C:\Users\%USERNAME%\%%a" "-oC:\Users\%USERNAME%" > nul 2> nul
    if !ERRORLEVEL! EQU 0 (
      echo "%%c %CN:~0,2%\%%~nxb unzip ok !"
    ) else (
      echo "%%c %CN:~0,2%\%%~nxb unzip Failed !"
    )
    setlocal DisableDelayedExpansion
  ) else (
    for /f "tokens=1 delims= " %%i in ('PsExec.exe -accepteula -nobanner \\%%c -s "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "Test-Path" "-Path" "C:\Users\%USERNAME%\%CN:~0,2%\%%~nxb" ^2^>nul') do (
      if %%i == True (
        psexec -accepteula -nobanner \\%%c -s "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "Remove-Item" "C:\Users\%USERNAME%\%CN:~0,2%\%%~nxb" "-Recurse" 2> nul
        psexec -accepteula -nobanner \\%%c -s "powershell" "-Command" "Expand-Archive" "-LiteralPath" "C:\Users\%USERNAME%\%%a" "-DestinationPath" "C:\Users\%USERNAME%" 2> nul
      ) else (
        psexec -accepteula -nobanner \\%%c -s "powershell" "-Command" "Expand-Archive" "-LiteralPath" "C:\Users\%USERNAME%\%%a" "-DestinationPath" "C:\Users\%USERNAME%" 2> nul
      )
    )
    for /f "tokens=1 delims= " %%i in ('PsExec.exe -accepteula -nobanner \\%%c -s "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "Test-Path" "-Path" "C:\Users\%USERNAME%\%CN:~0,2%\%%~nxb" ^2^>nul') do (
      if %%i == True (
      echo "%%c %CN:~0,2%\%%~nxb unzip ok !"
      ) else (
        echo "%%c %CN:~0,2%\%%~nxb unzip Failed !"
      )
    )
  )
)

:::

[註] %%~nxb,在範例中 %%b 為包含目錄的檔案名稱 : c1/alp.c1w1%%~nb - 顯示 alp (%%b 的 檔名) %%~xb - 顯示 .c1w1 ( %%b 的副檔名,包含 .)

執行解壓縮程式

> vmunzip.bat

螢幕輸出

"120.96.143.152 c1\alp.c1m1 unzip ok !"
"120.96.143.152 c1\alp.c1w1 unzip ok !"
"120.96.143.153 c1\alp.c1w2 unzip ok !"
"120.96.143.153 c1\alp.c1w3 unzip ok !"

6. 啟動與檢視 c1 叢集所有虛擬主機

start.bat 程式概述 : 使用 VMware 的 vmrun 命令,配合 psexec 命令將 遠端 Windows 實體主機中的 虛擬主機啟動

:::spoiler 完整程式內容

@echo off

REM 判斷叢集名稱
for /f "tokens=1" %%y in ('dir /B ^| findstr "mactohost"') do (
  set CN=%%y
)

REM 設定本機 IP 變數
for /f "delims=[] tokens=2" %%a in ('ping -4 -n 1 %ComputerName% ^| findstr [') do (
  set HostIP=%%a
)

setlocal EnableDelayedExpansion
for /f "tokens=6 delims= " %%a in (%CN:~0,2%mactohost) do (
  for /f "tokens=1" %%k in ('dir %USERPROFILE%\%CN:~0,2% /S /B ^| findstr vmx$') do (
    for /f "tokens=5,* delims=\" %%l in ("%%k") do (
      set hostvmx=%%m
    )
  )
  for /f "tokens=1" %%x in ('PsExec.exe -accepteula -nobanner \\%%a -s "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "Get-ChildItem" "-Path" "%USERPROFILE%\%CN:~0,2%" "-Name" "-Recurse" "-Force" ^2^>nul ^| findstr vmx$') do (
    for /f "tokens=1,* delims=\" %%y in ("%%x") do (
      set remotevmx=%%z
    )
  )
)

REM 啟動每一台虛擬主機
for /f "skip=12 tokens=4-6 delims= " %%a in (%CN:~0,2%mactohost) do (
  if %%c == %HostIP% (
    "C:\Program Files (x86)\VMware\VMware Player\vmrun" start "C:\Users\%USERNAME%\%CN:~0,2%\%%~nxb\%hostvmx%" nogui 2> nul > nul
    if !ERRORLEVEL! EQU 0 (
      echo "%CN:~0,2%\%%~nxb started"
    ) else (
      echo "%CN:~0,2%\%%~nxb failed"
    )
    timeout /t 5 > nul
  ) else (
    psexec \\%%c -accepteula -nobanner -s "C:\Program Files (x86)\VMware\VMware Player\vmrun" "start" "C:\Users\%USERNAME%\%CN:~0,2%\%%~nxb\%remotevmx%" "nogui" 2> nul > nul
    if !ERRORLEVEL! EQU 0 (
      echo "%CN:~0,2%\%%~nxb started"
    ) else (
      echo "%CN:~0,2%\%%~nxb failed"
    )
    timeout /t 5 > nul
  )
)

:::

1. 執行啟動 c1 叢集所有虛擬主機命令

> vmstart.bat

螢幕輸出

"c1\alp.c1m1 started"
"c1\alp.c1w1 started"
"c1\alp.c1w2 started"
"c1\alp.c1w3 started"

2. 在 Windows 檢視已開啟的虛擬電腦

list.bat 程式概述 : 透過 ssh 遠端對虛擬主機執行命令,並檢視 虛擬主機的狀態

:::spoiler 完整程式 :

@echo off

REM 判斷叢集名稱
for /f "tokens=1" %%y in ('dir /B ^| findstr "mactohost"') do (
  set CN=%%y
)

REM 先用 ping 命令,檢測每一台虛擬主機網路,有回應則用 ssh 命令遠端對虛擬主機執行命令
for /f "skip=12 tokens=4,6 delims= " %%a in (%CN:~0,2%mactohost) do ( 
  for /f "tokens=1-3 delims=." %%x in ('echo %%b') do (
    for /f "tokens=2 delims=." %%i in ('echo %%a') do (
      ping -n 2 "%%x.%%y.%%z.%%i" | find "TTL" 2> nul > nul
      if errorlevel 1 (
         echo "%%x.%%y.%%z.%%i not running"
      ) else (
        ssh -o "ConnectTimeout=5" -o "StrictHostKeyChecking=no" bigred@"%%x.%%y.%%z.%%i" "n=$(hostname;hostname -i); echo $n running" 2> nul
      )
    )
  )
)

:::

執行檢測命令

> vmlist.bat

螢幕輸出 :

c1m1 120.96.143.141 running
c1w1 120.96.143.145 running
c1w2 120.96.143.146 running
c1w3 120.96.143.147 running

[註] 如將 VM 開機後,遇到類似以下錯誤訊息

`8f: value too great for base (error token is "8f")`

請將 /home/bigred/bin/chnameip 程式中原本的 51 行命令 ip=$((16#$v)) ,修改為 ip=$(printf "%d\n" 0x"$v")

7. 關閉 c1 叢集所有虛擬主機

stop.bat 程式概述 : 利用 ssh 遠端對虛擬主機執行 sudo poweroff 的命令,將虛擬主機關閉

:::spoiler 完整程式內容

@echo off

REM 判斷叢集名稱
for /f "tokens=1" %%y in ('dir /B ^| findstr "mactohost"') do (
  set CN=%%y
)

REM 利用 ssh 遠端對每一台虛擬主機執行關機命令
setlocal EnableDelayedExpansion
for /f "skip=12 tokens=4,6 delims= " %%a in (%CN:~0,2%mactohost) do ( 
  for /f "tokens=1-3 delims=." %%x in ('echo %%b') do (
    for /f "tokens=2 delims=." %%i in ('echo %%a') do (
      ping -n 2 "%%x.%%y.%%z.%%i" | find "TTL" 2> nul > nul
      if errorlevel 1 (
         echo "%%x.%%y.%%z.%%i not running"
      ) else (
        ssh -o "ConnectTimeout=5" -o "StrictHostKeyChecking=no" bigred@"%%x.%%y.%%z.%%i" "sudo poweroff; exit" 2> nul > nul
        if !ERRORLEVEL! EQU 0 ( 
          echo "%%x.%%y.%%z.%%i stoped"
        ) else (
          if !ERRORLEVEL! EQU -1 echo "%%x.%%y.%%z.%%i stoped"
        )
      )
    )
  )
)

:::

執行關機 c1 叢集所有虛擬主機 命令

> vmstop.bat

螢幕輸出

"120.96.143.141 stoped"
"120.96.143.145 stoped"
"120.96.143.146 stoped"
"120.96.143.147 stoped"

8. 備份與還原 c1 叢集所有虛擬主機

backup.bat 程式概述 : 如果虛擬主機在本機,會透過 7z 壓縮,如果在遠端,會配合 psexec 命令,壓縮遠端的虛擬主機

:::spoiler 完整程式內容

@echo off

REM 判斷叢集名稱
for /f "tokens=1" %%y in ('dir /B ^| findstr "mactohost"') do (
  set CN=%%y
)

REM 設定本機 IP 變數
for /f "delims=[] tokens=2" %%e in ('ping -4 -n 1 %ComputerName% ^| findstr [') do (
  set HostIP=%%e
)

REM 先用 ping 命令,檢測每一台虛擬主機是否已關機
setlocal EnableDelayedExpansion
for /f "skip=12 tokens=4,6 delims= " %%a in (%CN:~0,2%mactohost) do ( 
  for /f "tokens=1-3 delims=." %%x in ('echo %%b') do (
    for /f "tokens=1,2 delims=." %%i in ('echo %%a') do (
      ping -n 2 "%%x.%%y.%%z.%%j" | find "TTL" 2> nul > nul
      if !ERRORLEVEL! == 0 (
         echo "Please Execute vmstop.bat" && GOTO:eof
      )
    )
  )
)

for /f "skip=12 tokens=4-6 delims= " %%a in (%CN:~0,2%mactohost) do (
  if %%c == %HostIP% (
    if not exist "%USERPROFILE%\7-Zip" (
      xcopy /E /Y ".\7z" "%USERPROFILE%\7-Zip\" > nul
    )
    "%USERPROFILE%\7-Zip\7z" -mx1 a "C:\Users\%USERNAME%\%%a.bak" "C:\Users\%USERNAME%\%%b" 2> nul > nul
      if !ERRORLEVEL! EQU 0 (
        echo "%%c %%a.bak backup ok !"
      ) else (
        echo "%%c %%a.bak backup failed !"
      )
  ) else (
    for /f "tokens=1 delims= " %%i in ('PsExec.exe -accepteula -nobanner \\%%c -s "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "Test-Path" "-Path" "%USERPROFILE%\7-Zip" ^2^>nul') do (
      if %%i == False (
        echo y | pscp -scp -pwfile passwd -q -r ".\7z" %USERNAME%@%%c:"%USERPROFILE%\7-Zip\"
      )
    )
    for /f "tokens=1 delims= " %%i in ('PsExec.exe -accepteula -nobanner \\%%c -s "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "Test-Path" "-Path" "C:\Users\%USERNAME%\%CN:~0,2%\%%~nxb" ^2^>nul') do (
      if %%i == True (
        psexec -accepteula -nobanner \\%%c -s "%USERPROFILE%\7-Zip\7z" "-mx1" "a" "C:\Users\%USERNAME%\%%a.bak" "C:\Users\%USERNAME%\%%b" 2> nul > nul
          if !ERRORLEVEL! EQU 0 (
            echo "%%c %%a.bak backup ok !"
          ) else (
            echo "%%c %%a.bak backup failed !"
          )
      ) else (
        echo "C:\Users\%USERNAME%\%CN:~0,2%\%%~nxb VM folder not found !"
        GOTO:eof
      )
    )
  )
)

:::

執行備份 c1 叢集所有虛擬主機 命令

> vmbackup.bat

螢幕輸出 :

"120.96.143.152 c1m1.141.zip.bak backup ok "
"120.96.143.152 c1w1.145.zip.bak backup ok "
"120.96.143.153 c1w2.146.zip.bak backup ok "
"120.96.143.153 c1w3.147.zip.bak backup ok "

執行失敗的螢幕輸出

"Please Execute vmstop.bat"    

可能原因 : 只要有一台 c1 cluster VM 沒關,就會噴出此錯誤訊息

[註] 新的備份檔會直接覆蓋掉舊的備份檔

執行還原 c1 叢集所有虛擬主機命令

restore.bat 程式概述 : 如果虛擬主機在本機,會透過 7z 解壓縮,如果在遠端,會配合 psexec 命令,解壓縮遠端的虛擬主機

:::spoiler 完整程式內容

@echo off

REM 判斷叢集名稱
for /f "tokens=1" %%y in ('dir /B ^| findstr "mactohost"') do (
  set CN=%%y
)

REM 設定本機 IP 變數
for /f "delims=[] tokens=2" %%e in ('ping -4 -n 1 %ComputerName% ^| findstr [') do (
  set HostIP=%%e
)


REM 先用 ping 命令,檢測每一台虛擬主機是否已關機
setlocal EnableDelayedExpansion
for /f "skip=12 tokens=4,6 delims= " %%a in (%CN:~0,2%mactohost) do ( 
  for /f "tokens=1-3 delims=." %%x in ('echo %%b') do (
    for /f "tokens=1,2 delims=." %%i in ('echo %%a') do (
      ping -n 2 "%%x.%%y.%%z.%%j" | find "TTL" 2> nul > nul
      if !ERRORLEVEL! == 0 (
         echo "Please Execute vmstop.bat" && GOTO:eof
      )
    )
  )
)

for /f "skip=12 tokens=4-6 delims= " %%a in (%CN:~0,2%mactohost) do (
  if %%c == %HostIP% (
    if exist "C:\Users\%USERNAME%\%CN:~0,2%\%%~nxb" (
      RD /S /Q "C:\Users\%USERNAME%\%CN:~0,2%\%%~nxb" > nul 2> nul
      "%USERPROFILE%\7-Zip\7z" x "C:\Users\%USERNAME%\%%a.bak" "-oC:\Users\%USERNAME%\%CN:~0,2%" > nul 2> nul
    ) else (
      "%USERPROFILE%\7-Zip\7z" x "C:\Users\%USERNAME%\%%a.bak" "-oC:\Users\%USERNAME%\%CN:~0,2%" > nul 2> nul
    )
    if exist "C:\Users\%USERNAME%\%CN:~0,2%\%%~nxb" (
      echo "%%c %CN:~0,2%\%%~nxb restore ok !"
    ) else (
      echo "%%c %CN:~0,2%\%%~nxb restore Failed !"
    )
  ) else (
    for /f "tokens=1 delims= " %%i in ('PsExec.exe -accepteula -nobanner \\%%c -s "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "Test-Path" "-Path" "C:\Users\%USERNAME%\%CN:~0,2%\%%~nxb" ^2^>nul') do (
      if %%i == True (
        psexec -accepteula -nobanner \\%%c -s "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "Remove-Item" "C:\Users\%USERNAME%\%CN:~0,2%\%%~nxb" "-Recurse" 2> nul
        psexec -accepteula -nobanner \\%%c -s "%USERPROFILE%\7-Zip\7z" "x" "C:\Users\%USERNAME%\%%a.bak" "-oC:\Users\%USERNAME%\%CN:~0,2%" > nul 2> nul
      ) else (
        psexec -accepteula -nobanner \\%%c -s "%USERPROFILE%\7-Zip\7z" "x" "C:\Users\%USERNAME%\%%a.bak" "-oC:\Users\%USERNAME%\%CN:~0,2%" > nul 2> nul
      )
    )
    for /f "tokens=1 delims= " %%i in ('PsExec.exe -accepteula -nobanner \\%%c -s "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "Test-Path" "-Path" "C:\Users\%USERNAME%\%CN:~0,2%\%%~nxb" ^2^>nul') do (
      if %%i == True (
        echo "%%c %CN:~0,2%\%%~nxb restore ok !"
      ) else (
        echo "%%c %CN:~0,2%\%%~nxb restore Failed !"
      )
    )
  )
)

:::

> vmrestore.bat

螢幕輸出 :

"120.96.143.152 c1\alp.c1m1 restore ok "
"120.96.143.152 c1\alp.c1w1 restore ok "
"120.96.143.153 c1\alp.c1w2 restore ok "
"120.96.143.153 c1\alp.c1w3 restore ok "

執行失敗的螢幕輸出

"Please Execute vmstop.bat"    

可能原因 : 只要有一台 c1 cluster VM 沒關,就會噴出此錯誤訊息

9. 清除 c1 叢集所有虛擬主機

del.bat 程式概述 : 如果虛擬主機在本地,會利用 Windows Powershell 的 Remove-Item 命令,刪除 c1 叢集所有虛擬主機、壓縮檔、本地的 c1 目錄,如果虛擬主機在遠端,會利用 Remove-Item 命令 配合 psexec 命令刪除

:::spoiler 完整程式內容 :

@echo off

REM 設定 Cluster Name
for /f "tokens=1" %%y in ('dir /B ^| findstr "mactohost"') do (
  set CN=%%y
)

REM 設定本機 IP 變數
for /f "delims=[] tokens=2" %%a in ('ping -4 -n 1 %ComputerName% ^| findstr [') do (
  set HostIP=%%a
)

REM 判斷是否已備份
for /f "tokens=4-6 delims= " %%a in ('type %CN:~0,2%mactohost ^| findstr zip') do (
  if %%c == %HostIP% (
    if not exist %HOMEPATH%\%%a.bak (
      echo "Please Execute vmbackup.bat first" && GOTO:eof
    )
  ) else (
    for /f "tokens=1 delims= " %%i in ('PsExec.exe -accepteula -nobanner \\%%c  -s "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "Test-Path" "-Path" "C:\Users\%USERNAME%\%%a.bak" ^2^>nul') do (
      if %%i == False (
        echo "Please Execute vmbackup.bat first" && GOTO:eof
      )
    )
  )
)

REM 先用 ping 命令,檢測每一台虛擬主機是否已關機
setlocal EnableDelayedExpansion
for /f "skip=12 tokens=4,6 delims= " %%a in (%CN:~0,2%mactohost) do ( 
  for /f "tokens=1-3 delims=." %%x in ('echo %%b') do (
    for /f "tokens=1,2 delims=." %%i in ('echo %%a') do (
      ping -n 2 "%%x.%%y.%%z.%%j" | find "TTL" 2> nul > nul
      if !ERRORLEVEL! == 0 (
         echo "Please Execute vmstop.bat" && GOTO:eof
      )
    )
  )
)

REM 將 d*mactohost 中 實體 Windows 主機的 IP 匯入 ip.txt 檔案中
if exist ip.txt (del ip.txt)
for /f "skip=12 tokens=6 delims= " %%y in (%CN:~0,2%mactohost) do echo %%y >> ip.txt

REM 刪除 ip.txt 檔案中每一台實體 Windows 電腦 使用者家目錄中以叢集為名稱的目錄
setlocal EnableDelayedExpansion
for /f "tokens=1 delims= " %%x in ('type ip.txt ^| sort.exe /unique') do (
  if %%x == %HostIP% (
    "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -Command Remove-Item C:\Users\%USERNAME%\%CN:~0,2%  -Recurse 2> nul
    if !ERRORLEVEL! EQU 0 (
      echo "%%x %CN:~0,2% folder deleted !"
    ) else (
      echo "%%x %CN:~0,2% folder does not exist !"
    )
  ) else (
    psexec -accepteula -nobanner \\%%x -s "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "Remove-Item" "C:\Users\%USERNAME%\%CN:~0,2%"  "-Recurse" 2> nul
    if !ERRORLEVEL! EQU 0 (
      echo "%%x %CN:~0,2% folder deleted !"
    ) else (
      echo "%%x %CN:~0,2% folder does not exist !"
    )
  )
)

REM 刪除在本地或遠端的虛擬主機壓縮檔
for /f "skip=12 tokens=4-6 delims= " %%a in (%CN:~0,2%mactohost) do (
  if %%c == %HostIP% (
    "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -Command Remove-Item C:\Users\%USERNAME%\%%a 2> nul
    if !ERRORLEVEL! EQU 0 (
      echo "%%c %%a deleted !"
    ) else (
      echo "%%c %%a does not exist !"
    )
  ) else (
    psexec -accepteula -nobanner \\%%c -s "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "Remove-Item" "C:\Users\%USERNAME%\%%a" 2> nul
    if !ERRORLEVEL! EQU 0 (
      echo "%%c %%a deleted !"
    ) else (
      echo "%%c %%a does not exist !"
    )
  )
)
del ip.txt 2> nul > nul

REM 刪除執行程式所在的目錄 (例如 : %USERPROFILE%\CNT.2023.v4.6\D9-1A2M3W\) 中的 d9 目錄
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -Command Remove-Item %CN:~0,2%  -Recurse 2> nul
if  %ERRORLEVEL% == 0 (
  echo "%HostIP% %CD%\%CN:~0,2% folder deleted !"
) else (
  echo "%HostIP% %CD%\%CN:~0,2% folder does not exist !"
)

:::

執行清除 c1 叢集所有虛擬主機命令

> vmdel.bat

螢幕輸出 :

"120.96.143.152 c1 folder deleted "
"120.96.143.153 c1 folder deleted "
"120.96.143.152 c1m1.141.zip deleted "
"120.96.143.152 c1w1.145.zip deleted "
"120.96.143.153 c1w2.146.zip deleted "
"120.96.143.153 c1w3.147.zip deleted "
"120.96.143.170 C:\Users\bigred\CNT.2023.v4.6\C1-1M3W\c1 folder deleted "

執行失敗的螢幕輸出

"Please Execute vmbackup.bat first"    

可能原因 : 程式一開始會檢查每一台 VM 對應的實體 Windows 主機的使用者家目錄,有無 *.bak 的備份檔,只要少一個備份檔,此程式就會噴出此錯誤訊息

[註] 會保留在本地或遠端實體主機的虛擬主機備份 (*.bak) 檔


IaC 平臺 開發關鍵技術

技術重點 1 - Windows batch script - 透過 FOR 命令可以將文字檔中的欄位資料取出

:star: 首先要注意 ! 如果要在批次程式中使用 FOR 命令,請指定 %%variable,而不要指定%variable。變數名稱有大小寫的區分,所以 %i 不同於 %I

逐行讀取文字檔的 for /F 迴圈

/f ,可以對 for 迴圈傳入的檔案、字串或命令,進行解析,以下為讀取檔案的範例 :

for /f "tokens=3,* delims= " %%a in (c1mactohost) do echo %%b
  • 概述:逐行讀取 k1mactohost 檔案的內容,然後依照分隔符號 (空格) 將檔案內容的值分別設為指定的變數,再將指定的變數內容輸出在螢幕上。
  • tokens=x,y,m-n 用來決定一次要取出幾個欄位,第一個欄位會存放在第一個自動變數,第二個欄位會存放在第二個自動變數裡,依此類推。在範例中為tokens=3,*,代表將第三個欄位的值存放在變數 a ,其他剩餘欄位的值會放在變數 b 註: x, x, m, n 都為數字,只是 m-n 是一種特別的表示法,代表一個解析欄位的範圍。例如 tokens=2-4 就代表取得第2、第 3 與第 4 個欄位的值並分別塞指定的變數裡
  • delims=xxx 用來決定欄位的分隔符號,預設為空格與 TAB 符號,並可自訂多個符號。在範例中,將欄位的分隔符號設為空格 註: xxx 代表多個字元,如果要同時設定小數點 . 與逗號 , 就要輸入delims=.,

練習

啟動一個文字檔 tip.txt

> notepad tip.txt 

內容是

10:6e d8a5 120.96.143.22
10:6f d8a6 120.96.143.22

寫一隻程式 (tip.bat)用冒號分割顯示以下的結果:

6e
6f

答案:

先編輯一個批次檔

$ notepad tip.bat

內容是

@echo off
for /f "tokens=1 delims= " %%a in (tip.txt) do echo %%a 

將 a 變數匯入一個檔案

@echo off
for /f "tokens=1 delims= " %%a in (tip.txt) do echo %%a >> t1.txt
for /f "tokens=2 delims=:" %%b in (t1.txt) do echo %%b

執行批次檔

$ tip.bat

螢幕輸出

6e
6f

另一種答案,雙迴圈

@echo off
for /f "tokens=1 delims= " %%a in (tip.txt) do (
  for /f "tokens=1,2 delims=:" %%b in ('echo %%a') do echo %%b %%c
)    

甚麼是 @echo off ?

認識 echo on

在 Windows batch script 中,預設是 echo on,當 Windows 電腦在執行批次程式中的每一行命令時,會先顯示其解析後的結果,才會再顯示執行後的結果

範例 :

編輯一個 test.bat 批次檔

> notepad test.bat

檔案內容

set a=b
echo %a%

執行程式

> test.bat 

螢幕輸出

>set a=b

>echo b
b

可以看到它在執行程式中的每一行命令時,會先顯示其解析後的結果,才會再顯示執行後的結果

認識 @echo off

@echo off 可以分為兩個部分,最前面的 @ 和 後面的echo off

  • @ 是針對特定命令,讓它不要顯示被解析後的結果,直接顯示命令執行後的結果
  • echo off 通常會被加在 Windows 批次檔的最上面,它的作用是,程式在執行時,只顯示命令執行結果

所以 @echo off ,它會不顯示自己被解析後的結果,且程式的每一行命令在執行時,只顯示命令執行結果。

延續上一個範例 :

編輯 test.bat 批次檔

> notepad test.bat

檔案內容的最上面新增 @echo off

@echo off
set a=b
echo %a%

執行程式

> test.bat 

螢幕輸出

b

甚麼是 setlocal EnableDelayedExpansion ?

在了解 甚麼是 setlocal EnableDelayedExpansion 之前,先來看看以下一段批次程式的內容

set VAR=before
if "%VAR%" == "before" (
    set VAR=after
    if "%VAR%" == "after" @echo "If you see this, it worked"
)

看完以後您覺得執行這隻程式的結果會顯示 "If you see this, it worked" 這段字串嗎?

來看看程式執行結果

> set VAR=before

> if "before" == "before" (
set VAR=after
 if "before" == "after"
)

在預設情況下,每行命令被執行之前,變數擴展成值,只會發生一次,即使你在 if 的括號之中,把它分成兩行。

通過 SETLOCAL EnableDelayedExpansion 命令打開 延遲擴展可以讓批次檔中的變數,在執行時擴展變成值,而不是在命令被解析的時後

使用 FOR 迴圈 或是 if 括號表達式等循環命令,延遲擴展將允許您始終讀取變數的當前值

現在我們在程式的最上面加上 SETLOCAL EnableDelayedExpansion

> notepad test.bat

檔案內容

SETLOCAL EnableDelayedExpansion

set VAR=before
if "%VAR%" == "before" (
    set VAR=after
    if "%VAR%" == "after"  @echo "If you see this, it worked"
)

執行程式

> test.bat

螢幕輸出

>test.bat

>SETLOCAL EnableDelayedExpansion

>set VAR=before

>if "before" == "before" (
set VAR=after
 if "before" == "after"
)

奇怪 ??? 為甚麼還是沒有在螢幕上輸出 “If you see this, it worked” 這段字串 ?

答案是因為 : 要讓延遲擴展生效,必須使用!variable_name!,在變數的前後都加驚嘆號,來立即讀取變數的值。

修改程式

> notepad test.bat

編輯程式內容,將 第二個 %VAR% 改成 !VAR!

@echo off
SETLOCAL EnableDelayedExpansion

set VAR=before
if "%VAR%" == "before" (
    set VAR=after
    if "!VAR!" == "after"  @echo "If you see this, it worked"
)

執行程式

> test.bat 

螢幕輸出

"If you see this, it worked"

成功 ~

參考文章

  1. 如何利用批次檔(Batch)讀取指令執行的結果或文字檔案內容
  2. for 迴圈
  3. 延遲變數展開 (EnableDelayedExpansion)

技術重點 2 - Windows 遠端啟動 VMware 虛擬電腦

1. 使用 psexec 必須使用此設定

開放遠端連線權限,用系統管理員身份打開 CMD,並執行以下命令

reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\system /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 /f

[註] 1. 機碼修改後,需重啟電腦        2. 如有安裝 F-Secure,請關閉 F-Secure 防火牆 參考文章連結 : PsTools 於 Windows 10 的環境設定

2. 執行 psexec 簡易命令

打開 CMD ,切換到 CNT.2023.v4.5\D9-1A2M3W工作目錄,執行以下測試命令

psexec \\192.168.39.55 -s "hostname"

螢幕輸出

PsExec v2.4 - Execute processes remotely
Copyright (C) 2001-2022 Mark Russinovich
Sysinternals - www.sysinternals.com


DESKTOP-VNDI6BN
hostname exited on 192.168.39.55 with error code 0.

3. 啟動虛擬機器

$ psexec \\120.96.143.3 -s -d "C:\Program Files (x86)\VMware\VMware Player\vmrun" "start" "C:\Users\bigred\alp.k1a1\alpine64.vmwarevm\alpine64.vmx" "nogui"

參數解釋 :

  • -s,遠端執行的命令將會由 System account 執行
  • -d,我們連線的實體主機即使斷線,所執行的命令不會因此中斷。您無法獲得任何退出代碼或輸出,因為遠程 PsExec 服務只是啟動程序,將新程序 ID 告知原始 PsExec 程序,而不再關心新程序的作用。

[stackoverflow 參考文章] What does psexec -s do?

4. 如何知道執行 psexec 成功

打開工作管理員,確認有沒有以下的顯示

5. 關閉虛擬機器

方法一 : 直接下命令

psexec \\120.96.143.5 -s -d "C:\Program Files (x86)\VMware\VMware Player\vmrun" "stop" "C:\Users\bigred\alp.k1w3\alpine64.vmwarevm\alpine64.vmx"

方法二 : 使用工作管理員在 vmware-vmx.exe 按右鍵結束工作

:::spoiler 參考圖片 :::

*.lck檔和類型是 VMEM檔案 的三個檔案刪掉

:::spoiler 參考圖片 :::

psexec 參考連結

技術參考連結