捐血一袋救人一命

江蘇拙政園

江蘇 拙政園

全家福

日本 和歌山城

賞楓之旅

千燈 夕照

水鄉千燈

蘆洲 微風運河

破曉時分

2024年3月13日 星期三

Powershell 變數型態_Hashtable

三種 Hash Table 的表示方式

為什麼要用 Hash Table 這種資料結構?
因為Powershell 可以很方便的將 Hash Table 陣列轉換換成 PSObject
而 PSObject可以很方便的轉換成各種格式,例如 .csv .html table .json .xml
$v1 = @{}
$v2 = New-Object -TypeName Hashtable
$v3= @{
	"Key1" = 3
	"Key2" = "String"
	"Key3" = @("array",5,"string")
}

$v2.Add("Key1",5)
$v2.Add("Key2","string")

$v2 | Format-List
$v3 | Format-Table

Hash Tables
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_hash_tables?view=powershell-5.1

2024年3月12日 星期二

Powershell 系統內建變數

Automatic Variables 自動變數 (系統內建變數)

變數 說明
$$
$? 查詢前一次指令的執行狀態
$^ 查詢前一次執行的指令
$_ 迴圈或是 Pipe 的暫存變數
$args
$ConsoleFileName
$Error
$Event
$EventSubscriber
$ExecutionContext
$true
$false
$null
$foreach
$HOME 使用者帳號家目錄路徑
$Host 用來判斷Powershell運行的環境
$input
$inputScript
$IsCoreCLR v7
$IsLinux v7 判斷作業系統是否為 Linux
$IsMacOS v7 判斷作業系統是否為 MacOS
$IsWindows v7 判斷作業系統是否為 Microsoft Windows
$LastExitCode
$Matches 正規式比對的結果
$MyInvocation
$PSScriptRoot
$PSCommandPath
$NestedPromptLevel
$PID 取得目前Powershell 或是 Powershell ISE 的 Process ID
$PROFILE
$PSBoundParameters
$PSCmdlet
$PSCulture 顯示系統的語系
$PSDebugCoNtext
$PSEdition
$PSHOME 顯示系統預設的 Powershell Home Path
$PSItem
$PSSenderInfo
$PSUICulture 顯示系統的語系
$PSVersionTable 顯示Powershell的版本資訊
$PWD 取得目前路徑位置
$Sender
$ShellId
$StackTrace
$switch
$this
$ast
$cursorColumn
$MaximumAliasCount
$MaximumDriveCount
$MaximumErrorCount
$MaximumFunctionCount
$MaximumVariableCount
$options
$positionOfCursor
$psISE 在 Powershell Console,此系統變數為 $null,可以用來判斷執行環境是 Powershell ISE 還是 Pwoershell Console
$psUnsupportedConsoleApplications
$tokens
$ConfirmPreference High, Medium, Low, None
$CurrentlyExecutingCommand
$DebugPreference Break: Enter the debugger, Continue: 顯示偵錯訊息,並繼續執行, Ignore: Ignore the evenet completely, SilentlyContinue: 沒有作用。 偵錯訊息不會顯示,而且不會中斷執行, Stop: 顯示偵錯訊息並停止執行。 將錯誤寫入主控台, Suspend: Reserved for future use.
$ErrorActionPreference
$ErrorView
$InformationPreference
$LogCommandHealthEvent
$LogCommandLifecycleEvent
$LogEngineHealthEvent
$LogEngineLifecycleEvent
$LogProviderHealthEvent
$LogProviderLifecycleEvent
$LogSettingsEvent
$PSLogUserData
$MaximumHistoryCount
$NestedPromptLevel
$OFS
$OutputEncoding
$ProgressPreference
$PSDebugContext
$PSDefaultParameterValues
$PSEmailServer
$PSItem
$PSModuleAutoLoadingPreference
$VerboseHelpErrors
$VerbosePreference
$WarningPreference
$WhatIfPreference
$Alias:
$Cert:
$Function:
$HKLM:
$HKCU:
$Variable:
$WSMan:
$Global:
$Local:
$Script:
$Private:
$env:ALLUSERSPROFILE C:\ProgramData
$env:APPDATA C:\Users\使用者帳號\AppData\Roaming
$env:ChocolateyInstall C:\ProgramData\chocolatey
$env:ChocolateyLastPathUpdate
$env:COMPUTERNAME 電腦名稱
$env:ComSpec C:\WINDOWS\system32\cmd.exe
$env:DriverData C:\Windows\System32\Drivers\DriverData
$env:HOMEDRIVE C:
$env:HOMEPATH \Users\使用者帳號
$env:LOCALAPPDATA C:\Users\使用者帳號\AppData\Local
$env:LOGONSERVER \電腦名稱
$env:NUMBER_OF_PROCESSORS 電腦CPU核心數
$env:OS Windows_NT
$env:Path Path環境變數
$env:PATHEXT 可執行的副檔名列表
$env:POWERSHELL_DISTRIBUTION_CHANNEL MSI:Windows 10 Pro
$env:PROCESSOR_ARCHITECTURE AMD
$env:PROCESSOR_IDENTIFIER Intel64 Family 6 Model 158 Stepping 10, GenuineIntel
$env:PROCESSOR_LEVEL 6
$env:PSModulePath Powershell Modules安裝的路徑
$env:SystemDrive C:
$env:SystemRoot C:\WINDOWS
$env:TEMP C:\Users\使用者帳號\AppData\Local\Temp
$env:USERNAME 使用者帳號
$env:USERPROFILE C:\Users\使用者帳號
$env:windir C:\WINDOWS
$env:ProgramFiles C:\Program Files
${env:ProgramFiles(x86)} C:\Program Files (x86)
$env:ProgramW6432 C:\Program Files

Powershell 命名規定

命名基本規定

  • Powershell 是不會區分大小寫
  • 系統有一些內建系統變數、環境變數、關鍵字,變數的命名上是不能使用的,不然變數值會被系統覆蓋掉。
  • 變數名稱中最好不要有特殊字元,例如:- (破折號),會被當成運算元減號,如果一定要用破折號,請將變數名稱用大括號括起,但這樣寫起來挺麻煩的,也容易出錯,還是不建議這樣搞!
${Saved-Items} = "a","b","c"
${Saved-Items}
  • Powershell Cmdlet/Function 命名有一定規矩,就是 Verb-Noun (動詞/動作-名詞),而且有規範只能使用哪些動詞(雖然,使用規範以外的動詞,不會發生甚麼錯誤,但是在 Visual Studio Code 裡執行時,會一直出現警告)
  • 如果要知道Powershell 規範哪些動詞,可以執行
Get-Verb

如果要知道,有哪些命令可以使用,可以執行

Get-Command -Noun restmethod*
Get-Command -Verb Split

如果要知道特定命令,有哪些組成、屬性、方法等,可以執行

Cmdlet | Get-Member

例如:

Get-Process | Get-Member -MemberType Method | Select-Object Name, Definition

enter image description here

查詢跟 CSV 格式相關的 Cmdlet

Get-Command -Noun CSV

查詢跟 JSON 格式相關的 Cmdlet

Get-Command -Noun JSON

查詢跟 HTML 格式相關的 Cmdlet

Get-Command -Noun HTML

查詢轉換格式相關的 Cmdlet

Get-Command -Verb Convert*

PowerShell 簡介

什麼是 PowerShell?

Powershell 是一個微軟發展的跨平台的語言,可以在 Windows、Linux、MacOS 上運行。
雖然號稱是跨平台,但是在不同的平台上,程式需要些許調整!
就我個人的經驗,在 Linux 平台上,可以運行 Powershell 但是,我還是會建議盡量使用 bash shell script 或是其他程式語言

PowerShell 能夠管理那些系統?

  • Microsoft Azure
  • Windows Server/Client
  • Exchange Server
  • SQL Server
  • Hyper-V
  • VMware
  • AWS
  • VMware
  • GCP

Powershell 能做那些事?

  • 呼叫 API 讀取網頁(無法讀取前端Javascript Render 的網頁資料)
  • 使用 Selenium 網頁爬蟲
  • 讀寫資料庫
  • 存取 FTP、SSH、SMTP、POP3 發送郵件(有些需要特過 Third Party)
  • 透過 Web Server GCI 執行 透過 HTTP Listener
  • 建立 Web Service (需要自己處理 MIME Type File)
  • 讀寫 CSV, JSON, XML, HTML
  • 壓縮/解壓縮 ZIP
  • 檔案管理
  • 註冊機碼管理
  • 連接/中斷網芳
  • 透過WinRM,管理遠端電腦
  • 載入 DLL,使用 Third Party Function

Powershell 流程控制

If

If (<test1>)
    {<statement list 1>}
[elseif (<test2>)
    {<statement list 2>}]
[else
    {<statement list 3>}]

簡略寫法

<condition> ? <if-true> : <if-false>

Switch

基本語法

Switch ( <test-expression> ){ 
	<result1-to-be-matched> {<action>} 
	<result2-to-be-matched> {<action>}
	Default {<action>} # optional
}

特別參數及語法

Switch [-regex | -wildcard | -exact] [-casesensitive] ( <test-expression> ){
    "string1" | number1 | variable1 | { <value-scriptblock1> } { <action-scriptblock> }
    "string2" | number2 | variable2 | { <value-scriptblock2> } { <action-scriptblock> }
    Default { <action-scriptblock> } # optional
}

從檔案取得資料

Switch [-regex | -wildcard | -exact] [-casesensitive] -file filename {
    "string" | number | variable | { <value-scriptblock> } { <action-scriptblock> }
    default { <action-scriptblock> }  # optional
}

說明:

參數 說明
-regex <test-expression> 的符合條件是正規式;如果 <test-expression> 變數型態不是字串,此參數會無效
-wildcard 使用萬用字元去比對,這裡不分大小寫;如果 <test-expression> 變數型態不是字串,此參數會無效
-exact 必須完全符合字串大小寫;如果 <test-expression> 變數型態不是字串,此參數會無效
-casesensitive 區分大小寫;如果 <test-expression> 變數型態不是字串,此參數會無效
-file 從文字型檔案讀取每一行來比較,這裡不分大小寫
  • 範例1:使用 Script Blocks 來媒合,Switch 會執行所有吻合條件的部分

    $a = 1,2,3
    $Variable = 2
    Switch($Variable){
    	{ $_ -in $a }{
            "Variable is in Array"
    	}
    	{ $_ -match "[0-9]" }{
            "Variable is matched regexp"
    	}
    	{ $_ -lt 10 -and $_ -gt 1 }{
            "Variable is bigger than 1, and smaller than 10"
    	}
    	Default {
            "Otherwise"
    	}
    }
    

    enter image description here

  • 範例二:要中斷Switch繼續媒合所有 Script Block,要使用 Break

    $a = 1,2,3
    $Variable = 2
    Switch($Variable){
    	{ $_ -in $a }{
            "Variable is in Array"
            break
    	}
    	{ $_ -match "[0-9]" }{
            "Variable is matched regexp"
            break
    	}
    	{ $_ -lt 10 -and $_ -gt 1 }{
            "Variable is bigger than 1, and smaller than 10"
            break
    	}
    	Default {
            "Otherwise"
    	}
    }
    

    enter image description here

  • 範例三:

    $Variable = 42
    Switch($Variable){
    	{ 0..20 -contains $_ }{
            "Variable is between 0~20"
    	}
    	{ 21..40 -contains $_ }{
            "Variable is between 21~40"
    	}
    	{ 41..60 -contains $_ }{
            "Variable is between 41~60"
    	}
    	{ 61..80 -contains $_ }{
            "Variable is between 61~80"
    	}
    	{ 81..100 -contains $_ }{
            "Variable is between 81~100"
    	}
    	Default{
            "Variable is not in 0~100"
    	}
    }
    

    enter image description here

  • 範例四:媒合條件為正規式

    $month = (Get-Date).Month
    $month
    switch -regex ([string]$month) {
     "^[1-3]$" {"Q1"}
     "^[4-6]$" {"Q2"}
     "^[7-9]$" {"Q3"}
     "^1[0-2]$" {"Q4"}
    }
    

    enter image description here

Comparison Operators 比較運算說明

比較 說明
-and 進行邏輯運算 AND
-or 進行邏輯運算 OR
-not 進行邏輯運算 NOT
-xor 進行邏輯運算 XOR,而非 bit XOR運算
-is 判斷變數型態是否與比對的型態相同
-isnot 判斷變數型態是否與比對的型態不相同
-like 字串匹配通配符模式,忽略大小寫
-ilike 字串匹配通配符模式,忽略大小寫
-clike 字串匹配通配符模式,比較大小寫
-notlike 字串與通配符模式不匹配,忽略大小寫
-inotlike 字串與通配符模式不匹配,忽略大小寫
-cnotlike 字串與通配符模式不匹配,比較大小寫
-match 字串與正規表示式模式匹配,忽略大小寫
-imatch 字串與正規表示式模式匹配,忽略大小寫
-cmatch 字串與正規表示式模式匹配,比較大小寫
-notmatch 字串與正規表示式模式不匹配,忽略大小寫
-inotmatch 字串與正規表示式模式不匹配,忽略大小寫
-cnotmatch 字串與正規表示式模式不匹配,比較大小寫
-in 變數值存在於集合之中,忽略大小寫
-iin 變數值存在於集合之中,忽略大小寫
-cin 變數值存在於集合之中,比較大小寫
-notin 變數值不存在於集合之中,忽略大小寫
-inotin 變數值不存在於集合之中,忽略大小寫
-cnotin 變數值不存在於集合之中,比較大小寫
-contains 集合中包含變數值,忽略大小寫
-icontains 集合中包含變數值,忽略大小寫
-ccontains 集合中包含變數值,比較大小寫
-eq Equal 等於(忽略大小寫)
-ieq Equal 等於,忽略大小寫
-ceq Equal 等於,比較大小寫
-ne Not Equal 不等於,忽略大小寫
-ine Not Equal 不等於,忽略大小寫
-cne Not Equal 不等於,比較大小寫
-gt Great Than 大於,忽略大小寫
-igt Great Than 大於,忽略大小寫
-cgt Great Than 大於,比較大小寫
-ge Great Equal 大於等於,忽略大小寫
-ige Great Equal 大於等於,忽略大小寫
-cge Great Equal 大於等於,比較大小寫
-lt Less Than 小於,忽略大小寫
-ilt Less Than 小於,忽略大小寫
-clt Less Than 小於,比較大小寫
-le Less Equal 小於等於,忽略大小寫
-ile Less Equal 小於等於,忽略大小寫
-cle Less Equal 小於等於,比較大小寫
  • 仔細觀察可以發現,只要加上 i 就是不區分大小寫;加上 c 就是區分大小寫

  • 從範例來理解 -in & -contains

    $a = 5
    
    1..3 -contains $a
    1..10 -contains $a
    
    $a -in 1..3
    $a -in 1..10
    
  • 從範例來了解 -is & -isnot

    $a = 5
    $b = "5"
    $a.GetType()
    $b.GetType()
    $a -is [int]
    $a -isnot $b.GetType()
    

    enter image description here

  • 需要特別注意的特例

$a = 1, 2, $null, 4, $null, 6
$a.GetType()
$a -ne $null

$null -ne $a

enter image description here

"abc" -eq "abc"         # Output: True
"abc" -eq "abc", "def"  # Output: False
"abc" -ne "def"         # Output: True
"abc" -ne "abc"         # Output: False
"abc" -ne "abc", "def"  # Output: True

"abc", "def" -eq "abc"  # Output: abc
"abc", "def" -ne "abc"  # Output: def

enter image description here
說明:當集合在運算子左邊時,其結果會是過濾變數值

  • 再來一個自訂類別的比較
class MyFileInfoSet {
    [String]$File
    [Int64]$Size
}
$a = [MyFileInfoSet]@{File = "C:\Windows\explorer.exe"; Size = 4651032}
$b = [MyFileInfoSet]@{File = "C:\Windows\explorer.exe"; Size = 4651032}
$a -eq $b

上面程式碼雖然透過自訂類別,設定了兩個變數,兩個變數的屬性值雖然都一樣,但這兩個變數是不同的物件, -eq 會認為這不是同一個物件。

所以要讓 -eq 能判斷物件"值" 是否一致,要改用以下程式,讓程式知道使用 -eq 比較時,是要比較兩個變數的值

class MyFileInfoSet : System.IEquatable[Object]{ 
	[String]$File 
	[Int64]$Size 
	[bool] Equals([Object] $obj){ 
		return ($this.File -eq  $obj.File) -and ($this.Size -eq  $obj.Size) 
	}
}
$c = [MyFileInfoSet]@{File = "C:\Windows\explorer.exe"; Size = 4651032}
$d = [MyFileInfoSet]@{File = "C:\Windows\explorer.exe"; Size = 4651032}
$c -eq  $d

我們再來看看兩個類別宣告出來的物件變數的方法有甚麼不同

class MyFileInfoSet {
    [String]$File
    [Int64]$Size
}
$a = [MyFileInfoSet]@{File = "C:\Windows\explorer.exe"; Size = 4651032}
$a | Get-Member

class MyFileInfoSet2 : System.IEquatable[Object]{ 
	[String]$File 
	[Int64]$Size 
	[bool] Equals([Object] $obj){ 
		return ($this.File -eq  $obj.File) -and ($this.Size -eq  $obj.Size) 
	}
}

$c = [MyFileInfoSet2]@{File = "C:\Windows\explorer.exe"; Size = 4651032}
$c | Get-Member

($c | Get-Member) | Where-Object { $_.Name -eq "Equals" } | Select-Object -Property Definition

enter image description here

Break、Continue、Return、Exit

Break 中斷迴圈或是 Switch,或是 Trap

  • 範例一:

    $i=0
    $varB = 10,20,30,40
    foreach ($val in $varB) {
      if ($val -eq 30) {
        break
      }
      $i++
    }
    Write-Host "30 was found in array index $i"
    
  • 範例二:中斷指定的迴圈(這是很差的程式習慣,請盡量不要使用)

    Remove-Variable * -ErrorAction SilentlyContinue
    $b = 3
    :red while ($true) {
      :yellow while ($true) {
        while ($true) {
          if ($a) {break}
          if ($b) {break yellow}
          if ($c) {break red}
        }
        Write-Host "中斷最內層迴圈"
      }
      Write-Host "中斷 :yellow 迴圈"
    }
    Write-Host "中斷 :red 迴圈"
    

如果有定義 $a,就會中斷最內層迴圈
如果有定義 $b,就會中斷 :yellow 迴圈
如果有定義 $c,就會中斷 :red 迴圈

  • 範例三:
    function test {
      trap [DivideByZeroException] {
        Write-Host 'divide by zero trapped'
        break
      }
    
      $i = 3
      'Before loop'
      while ($true) {
         "1 / $i = " + (1 / $i--)
      }
      'After loop'
    }
    test
    
    enter image description here
    當除數為0的例外被觸發時,印出 “divide by zero trapped”
    然後就終止函式
    如果沒有使用 Break,就會在發生錯誤之後,繼續執行印出 “After loop”

請不要在迴圈(不包含 ForEach-Object)、Switch、Trap 之外使用 Break!這會目前的 Runspace 被終止!

function test {
 "TEST"
 break
}

test
"After Test"

當程式執行 test 函式時,輸出 “TEST” 之後,整個程式就被 break 終止了!所以 “After Test” 永遠不會被執行到。

1..10 | ForEach-Object{
    If ($_ -eq 3){
        Break
    }
    $_
}

"迴圈之後,繼續執行"

在 ForEach-Object 內使用 Break,也會導致整個程式被終止!

ForEach ($v in 1..10){
    If ($v -eq 3){
        Break
    }
    $v
}

"迴圈之後,繼續執行"

但是在 ForEach ($variable in <collection>){} 迴圈卻不會中斷整個程式

https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_continue?view=powershell-7.3

Powershell 迴圈的額外說明

Powershell 迴圈的額外說明

以下三種 Powershell 迴圈程式,先試著猜猜結果為何

Function loop1{
    1..3 | ForEach-Object{
        If ($_ -eq 2){
            Return "Y", $_
        }Else{
            Return "N", $_
        }
    }
}

loop1
Function loop2{
    For($i=1; $i -le 3; $i++){
        If ($_ -eq 2){
            Return "Y", $i
        }Else{
            Return "N", $i
        }
    }
}

loop2
Function loop3{
    ForEach($num in 1..3){
        If ($_ -eq 2){
            Return "Y", $num
        }Else{
            Return "N", $num
        }
    }
}

loop3

當你分別執行上面的程式之後,會發現
ForEach-Object{ }
會跑完每一個 Object,所以它會回傳三次!
而 For 或是 ForEach 迴圈,則是如同預期的,只會跑一次

這個 ForEach-Object { } 的用法,就很像 Python 的 yield

Powershell 迴圈

Powershell 迴圈

第一種迴圈

For (<Init 變數初始值>; <Condition 迴圈繼續執行條件>; <Repeat/Step 變數增減運算式>){
    <Statement list>
}

例如:
設定變數 $v = 1,$a = 10
當 $v 數值小於等於 5 時,執行迴圈內指令
變數 $v 的值,每次累加 2

For ($v, $a=1,10; $v -le 5; $v = $v + 2){
    "`$v = $v"
    $a += $v
    "`$a = $a"
}

因為要程式輸出 $,所以 $ 前面要加上 ` 跳脫符號
enter image description here

第二種迴圈

ForEach (<item> in <collection>){
    <Statement list>
}

第三種迴圈

先判斷是否吻合條件

While (<condition){
	<Statement list>
}

第四種迴圈

至少會執行一次,執行後判斷是否吻合條件,條件吻合時繼續執行

Do {
	<Statement list>
} While (<condition>)

第五種迴圈

至少會執行一次,執行後判斷是否吻合條件,條件吻合時結束執行

Do {
	<Statement list>
} Until (<condition>)

第六種迴圈

透過 Pipe 來源,對每個 Object 執行 Statement list

| ForEach-Object{
	<Statement list>
}
$Events = Get-EventLog -LogName System -Newest 1000
$events | ForEach-Object -Begin {Get-Date} -Process {Out-File -FilePath Events.txt -Append -InputObject $_.Message} -End {Get-Date}
Get-Module -ListAvailable | ForEach-Object -MemberName Path

執行字串物件的 Split 方法,參數 “.”

"Microsoft.PowerShell.Host" | ForEach-Object -MemberName Split -ArgumentList  "."
1..2 | ForEach-Object -Begin { '開始' } -Process { '程序 A' } { '程序 B' } -End { '結束' }

Begin 與 End Script Blocks 都只會執行一次,而 Process 後面可以接多個 Script Blocks,會依照傳遞物件的數量來執行次數

以下只有 Powershell v7 才有

$Message = "Output:"

1..8 | ForEach-Object -Parallel {
    "$using:Message $_"
    Start-Sleep 10
} -ThrottleLimit 10

採用多執行緒 -Parallel
執行緒數量 -ThrottleLimit 10
所以程式會一次把 1~8 都輸出,最後等上 10秒才結束程式

如果修改 -ThrottleLimit 為 2,就會每次只輸出兩個數值,並等上 10秒

取得系統事件紀錄(需要以管理者權限執行)

$logNames = 'Security','Application','System','Windows PowerShell','Microsoft-Windows-Store/Operational'

$logEntries = $logNames | ForEach-Object -Parallel {
    Get-WinEvent -LogName $_ -MaxEvents 10000
} -ThrottleLimit 5

$logEntries.Count

當使用多執行緒時,要注意系統資源使用量會拉高!

$job = 1..10 | ForEach-Object -Parallel {
    "Output: $_"
    Start-Sleep 3
} -ThrottleLimit 2 -AsJob

$job | Receive-Job -Wait

使用多執行緒,並丟到背景執行,然後用 Receive-Job -Wait 等待並取得執行結果