# 2023 Windows & VMware IaC 開發及使用手冊
<style>
.indent-title-1{
margin-left: 1em;
}
.indent-title-2{
margin-left: 2em;
}
.indent-title-3{
margin-left: 3em;
}
</style>
## 緣由
要**手動部屬**多台虛擬主機時,有高機率造成 **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 主機
參考文章連結 :
- [Understanding Infrastructure as Code (IaC) in less than 10 minutes](https://www.novatec-gmbh.de/en/blog/understanding-infrastructure-as-code-iac-in-less-than-10-minutes/)
- [何謂基礎架構程式碼 (IaC)?](https://www.trendmicro.com/zh_tw/what-is/cloud-security/infrastructure-as-code.html)
- [What is Infrastructure as Code (IaC)?](https://www.redhat.com/en/topics/automation/what-is-infrastructure-as-code-iac)
---
# <font color=red>Windows & VMware IaC 平台設計</font>
## <font color=blue>平台目標</font>
### **自動化建置與管理分散型叢集系統基礎架構**
以 Kubernetes 架構範例 :
- 一台 Master 主機
- 三台 Worker 主機
以 Hadoop 架構範例 :
- 一台 Admin 機器
- 兩台 Master 機器
- M1 跑 Name Node, Secondary Name Node
- M2 跑 Resource Manager, Jobhistory Server
- 三台 Worker 機器
- 每一台機器都會跑 Data Node, Node Manager
## <font color=blue>重要操作步驟</font>
1. 設計叢集架構 (`mactohost`)
2. 產生虛擬電腦資料夾
3. 壓縮虛擬電腦資料夾
4. 派送虛擬電腦資料夾
5. 解壓縮所有虛擬主機壓縮檔
6. 啟動與檢視所有虛擬主機
7. 關閉所有虛擬主機
8. 備份與還原所有虛擬主機
9. 清除所有虛擬主機
## <font color=blue>實體電腦硬體規格</font>
- 多台實體 Windows 10 電腦
- 記憶體至少 24 G
- CPU: 4 C / 2 HT 以上
- 須為靜態 IPv4 的 IP 位址
- 每台實體 Windows 10 電腦會分別跑一台或多台不同的虛擬電腦
## <font color=blue>虛擬電腦硬體規格</font>
- Linux 作業系統
- 記憶體 : 依建置叢集需求設定
- CPU : 依建置叢集需求設定
- 網路模式 : bridge
## <font color=blue>虛擬電腦網路設定</font>
根據叢集的大小來規劃 IP 位址、 Subnetmask 以及 Default Gateway
---
# <font color=red>開始建置 C1 Cluster (1M3W)</font>
## <font color=blue>建置叢集系統前準備</font>
<div class="indent-title-">
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 官網連結](https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse?tabs=powershell#install-openssh-for-windows)
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 的環境設定](http://pejslin.blogspot.com/2019/11/pstools-windows-10.html)
7. 確認 Windows 主機上當前的使用者家目錄下 ssh 的私鑰 `%USERPROFILE%\.ssh\id_rsa` 權限是否太開放,
:::spoiler 示意圖

:::
如果在群組或其他使用者這個欄位下,有其他人,那麼請執行以下命令 :
```
> cd %USERPROFILE%\CNT.2023.v4.6\C1-1M3W && sshsetup.bat
```
螢幕輸出 :
```
SSH key Setup ok !
```
:::spoiler 示意圖

:::
</div>
## <div class="indent-title-1"><font color=blue>1. 設計 c1 叢集架構</font></div>
### <div class="indent-title-2">設定 c1 叢集架構檔 - `mactohost`</div>
<div class="indent-title-3">
在 Windows 中,打開 CMD ,執行以下命令
切換至工作目錄
```
> cd CNT.2023.v4.6\C1-3M3W
```
編輯 `c1mactohost`
```
> notepad c1mactohost
```
檔案內容 :
```bash=
# 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 行內容,
<pre># K8S <font color=red>c1</font> Cluster <font color=red>ALP.K8S</font></pre>
- 第三欄 <font color=red>`c1`</font> 會與 `CNT.2023.V4.6\C1-1A3M\c1mactohost` 這個檔案的前兩個字 `c1` 比較,所以不能亂設定
- 第五欄 <font color=red>`ALP.K8S`</font> 設定 模板機的目錄名稱
- 模板機必須放在 `%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 位址
</div>
## <div class="indent-title-1"><font color=blue>2. 建立 c1 叢集所有虛擬主機</font></div>
<div class="indent-title-3">
create.bat 程式概述 : 在落地雲主機的 `C:\Users\bigred\CNT.2023.v4.6\C1-1M3W` 目錄下,做出 c1 的資料夾,並在 c1 目錄下建立 4 台虛擬機
**[重要] 根據實體電腦的記憶體來修改 vmcreate.bat 中 RSMMAX 變數直的大小**
:::spoiler 完整程式內容
```bash!
@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 叢集命令
在執行命令前,請先確定是否已完成 [叢集系統準備](https://hackmd.io/NyMWv5u5Rlm-ip-9CHi3Tg?both#%E5%8F%A2%E9%9B%86%E7%B3%BB%E7%B5%B1%E6%BA%96%E5%82%99)
在 Windows 工作目錄 `CNT.2023.v4.6\c1-1M3W` 下,執行程式
```bash!
> 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](https://superuser.com/questions/1296024/windows-ssh-permissions-for-private-key-are-too-open)
</div>
## <div class="indent-title-1"><font color=blue>3. 壓縮 c1 叢集所有虛擬主機</font></div>
<div class="indent-title-3">
zip.bat 程式概述 : 此程式使用 7z 作為虛擬主機目錄的壓縮命令
:::spoiler 完整程式內容
```bash!
@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
```
:::
### 執行壓縮程式
```bash!
> vmzip.bat
```
螢幕輸出
```!
"c1m1.141.zip ok"
"c1w1.145.zip ok"
"c1w2.146.zip ok"
"c1w3.147.zip ok"
```
</div>
## <div class="indent-title-1"><font color=blue>4. 派送 c1 叢集所有虛擬主機</font></div>
<div class="indent-title-3">
scp.bat 概述 : 使用 pscp.exe 命令,將 c1 叢集每一台虛擬主機的壓縮檔派送到 mactohost 中設定對應的各台實體 Windows 主機
:::spoiler 完整程式內容
```bash!
@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
```
:::
### 執行程式
```bash!
> 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"
```
</div>
## <div class="indent-title-1"><font color=blue>5. 解壓縮所有虛擬主機壓縮檔至落地雲主機</font></div>
<div class="indent-title-3">
unzip.bat 程式概述 : 本程式在本機使用 7z 將 虛擬主機的壓縮檔解壓縮,遠端的主機使用 Windows Powershell 的 Expand-Archive 命令
:::spoiler 完整程式內容
```bash!
@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` 的副檔名,包含 `.`)
### 執行解壓縮程式
```bash!
> 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 !"
```
</div>
## <div class="indent-title-1"><font color=blue>6. 啟動與檢視 c1 叢集所有虛擬主機</font></div>
<div class="indent-title-3">
start.bat 程式概述 : 使用 VMware 的 vmrun 命令,配合 psexec 命令將 遠端 Windows 實體主機中的 虛擬主機啟動
:::spoiler 完整程式內容
```bash!
@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 叢集所有虛擬主機命令
```bash!
> 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")`
</div>
## <div class="indent-title-1"><font color=blue>7. 關閉 c1 叢集所有虛擬主機</font></div>
<div class="indent-title-3">
stop.bat 程式概述 : 利用 ssh 遠端對虛擬主機執行 sudo poweroff 的命令,將虛擬主機關閉
:::spoiler 完整程式內容
```bash!
@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 叢集所有虛擬主機 命令
```bash!
> vmstop.bat
```
螢幕輸出
```!
"120.96.143.141 stoped"
"120.96.143.145 stoped"
"120.96.143.146 stoped"
"120.96.143.147 stoped"
```
</div>
## <div class="indent-title-1"><font color=blue>8. 備份與還原 c1 叢集所有虛擬主機</font></div>
<div class="indent-title-3">
backup.bat 程式概述 : 如果虛擬主機在本機,會透過 7z 壓縮,如果在遠端,會配合 psexec 命令,壓縮遠端的虛擬主機
:::spoiler 完整程式內容
```bash!
@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 完整程式內容
```bash!
@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 沒關,就會噴出此錯誤訊息
</div>
## <div class="indent-title-1"><font color=blue>9. 清除 c1 叢集所有虛擬主機</font></div>
<div class="indent-title-3">
del.bat 程式概述 : 如果虛擬主機在本地,會利用 Windows Powershell 的 Remove-Item 命令,刪除 c1 叢集所有虛擬主機、壓縮檔、本地的 c1 目錄,如果虛擬主機在遠端,會利用 Remove-Item 命令 配合 psexec 命令刪除
:::spoiler 完整程式內容 :
```bash!
@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`) 檔**
</div>
---
# <font color=red>IaC 平臺 開發關鍵技術 </font>
## <font color=blue>技術重點 1 - Windows batch script - 透過 `FOR` 命令可以將文字檔中的欄位資料取出</font>
<div class="indent-title-1">
:star: 首先要注意 ! 如果要在批次程式中使用 `FOR` 命令,請指定 <font color=red>`%%variable`</font>,而不要指定<font color=red>`%variable`</font>。變數名稱有大小寫的區分,所以 <font color=red>`%i`</font> 不同於 <font color=red>`%I`</font>。
</div>
### 逐行讀取文字檔的 `for /F` 迴圈
<div class="indent-title-1">
`/f` ,可以對 for 迴圈傳入的檔案、字串或命令,進行解析,以下為讀取檔案的範例 :
```bash!
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=.,`
</div>
### 練習
<div class="indent-title-1">
啟動一個文字檔 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
)
```
</div>
### 甚麼是 `@echo off` ?
<div class="indent-title-1">
#### 認識 echo on
<div class="indent-title-1">
在 Windows batch script 中,預設是 `echo on`,當 Windows 電腦在執行批次程式中的每一行命令時,會先顯示其解析後的結果,才會再顯示執行後的結果
範例 :
編輯一個 test.bat 批次檔
```
> notepad test.bat
```
檔案內容
```
set a=b
echo %a%
```
執行程式
```
> test.bat
```
螢幕輸出
```
>set a=b
>echo b
b
```
可以看到它在執行程式中的每一行命令時,會先顯示其解析後的結果,才會再顯示執行後的結果
</div>
#### 認識 @echo off
<div class="indent-title-1">
`@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
```
</div>
</div>
### 甚麼是 setlocal EnableDelayedExpansion ?
<div class="indent-title-1">
在了解 甚麼是 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"
```
成功 ~
</div>
### 參考文章
1. [如何利用批次檔(Batch)讀取指令執行的結果或文字檔案內容](https://blog.miniasp.com/post/2010/09/24/How-to-parse-text-from-file-or-command-using-Batch)
2. [for 迴圈](https://peterju.gitbooks.io/cmddoc/content/loop.html)
3. [延遲變數展開 (EnableDelayedExpansion)](https://ss64.com/nt/delayedexpansion.html)
## <font color=blue>技術重點 2 - Windows 遠端啟動 VMware 虛擬電腦</font>
### 1. 使用 psexec 必須使用此設定
<div class="indent-title-2">
開放遠端連線權限,用系統管理員身份打開 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 的環境設定](http://pejslin.blogspot.com/2019/11/pstools-windows-10.html)
</div>
### 2. 執行 `psexec` 簡易命令
<div class="indent-title-2">
打開 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.
```
</div>
### 3. 啟動虛擬機器
<div class="indent-title-2">
```bash!
$ 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?](https://stackoverflow.com/questions/48981961/what-does-psexec-s-do)
</div>
### 4. 如何知道執行 `psexec` 成功
<div class="indent-title-2">
打開工作管理員,確認有沒有以下的顯示

</div>
### 5. 關閉虛擬機器
<div class="indent-title-2">
方法一 : 直接下命令
```bash!
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 參考圖片

:::
</div>
### psexec 參考連結
- [Everything You Wanted to know About Psexec](https://adamtheautomator.com/psexec/)
## <font color=blue>技術參考連結</font>
- [Compress-Archive - Microsoft 官網連結](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.archive/compress-archive?view=powershell-7.3)
- [Understanding Infrastructure as Code (IaC) in less than 10 minutes](https://www.novatec-gmbh.de/en/blog/understanding-infrastructure-as-code-iac-in-less-than-10-minutes/)
- [Running Remote Commands](https://learn.microsoft.com/en-us/powershell/scripting/learn/remoting/running-remote-commands?view=powershell-7.3)
- [Using PsExec to Run Commands Remotely](https://theitbros.com/using-psexec-to-run-commands-remotely/)