捐血一袋救人一命

江蘇拙政園

江蘇 拙政園

全家福

日本 和歌山城

賞楓之旅

千燈 夕照

水鄉千燈

蘆洲 微風運河

破曉時分

顯示具有 PowerShell 標籤的文章。 顯示所有文章
顯示具有 PowerShell 標籤的文章。 顯示所有文章

2024年3月23日 星期六

Powershell 變數特性

Power Shell 是一個弱型別的 Script Language

不需要宣告,變數存甚麼,就是甚麼型態

$a="123"
$a.GetType()

$a = 456
$a.GetType()

Result:

IsPublic IsSerial Name    BaseType                              
-------- -------- ----    --------                              
True     True     String  System.Object                         
True     True     Int32   System.ValueType

要注意的是,變數初始值設定的方法

$a, $b = 1, 2 ,3

Result:

$a = 1      # 數值
$b = 2, 3   # 是陣列

function abc{
    return 1,"A","C"
}

$a, $b = abc
$a.GetType()
$b.GetType()

Result:

IsPublic IsSerial Name       BaseType                              
-------- -------- ----       --------                              
True     True     Int32      System.ValueType                      
True     True     Object[]   System.Array 

一個變數在沒有使用之前,變數等於 $null

Clear-Host
Remove-Variable * -ErrorAction SilentlyContinue

$var -eq $null
$var = 3
$var -eq $null
Clear-Variable var
$var -eq $null

Result:

True
False
True

Powershell 變數型態_String

Powershell 的字串,可以使用對稱的 雙引號,或是單引號

* 如果字串本身包含大括號 { 或 },就要改成 {{ 或 }}
* 如果字串本身有雙引號,就要使用 Double 雙引號 ""
* 如果字串本身有單引號,就要使用 Double 單引號 ''
* 經過格式化字串之後,得到的資料型態為字串
* 如果字串本身有 $,請使用 Double $ 或是跳脫字元 `
* 如果字串本身有 `,請使用 Double `
* 如果字串中要輸出跳行,請使用 `r`n
* 如果字串中要輸出TAB,請使用 `t

例如:

"他的外號叫做""豬頭"""

Result:

他的外號叫做"豬頭"

格式化字串

格式化字串的方法

第一種方式,字串中夾變數

$age = 52
"我的年齡是 $age 歲"

Result:

我的年齡是 52 歲

第二種方式

"格式字串" -f 參數

第三種方法

[string]::format("格式字串", 參數)

數值格式化字串

代號 說明
#
nm m 表示小數位數,不足會補零對齊
dm m 表示數值位數,不足的位數會補零

"{0:n2}" -f 123.5
"{0:n2}" -f 3.14

Result:

123.50
3.14

"{0:d2}" -f 123
"{0:d2}" -f 1

Result:

123
01

"{0:0##,###.#0}" -f 12345.5
"{0:0##,###.#0}" -f 123456.7
"{0:0##,###.#0}" -f 1234567.579

Result:

  • 整數位數,不足會補零
  • 小數位數,不足會補零
  • 小數位數超過的,會四捨五入
012,345.50
123,456.70
1,234,567.58

日期型態資料的格式化字串

代號 說明
y yyyy年MM月
yy yy 兩位數西元年
yyyy yyyy 四位數西元年
M MM月dd日
MM MM 兩位數月份,不足兩位數,前面會補0
d yyyy/MM/dd
dd dd 兩位數日期,不足兩位數,前面會補0
hh 兩位數12小時制(不足兩位數,前面會補0)不會顯示 AM/PM 或上午/下午
HH 兩位數24小時制(不足兩位數,前面會補0)
mm 兩位數分鐘(不足兩位數,前面會補0)
s yyyy-MM-ddTHH:mm:ss
ss 兩位數秒(不足兩位數,前面會補0)
f yyyy年M月dd日 上午/下午 hh:mm
ff 兩位數毫秒
fff 三位數毫秒
ddd 周一、周二、周三…
dddd 星期一、星期二、星期三…

2024年3月13日 星期三

Powershell 變數型態_Array

Array

  • 同一陣列內,可以混合儲存各種形態資料
  • 陣列起始註標為 0

陣列的表示法

$a1 = 1..3
$a2 = 1,2,3
$a3 = @(1,2,3)
$a4 = (1..3), "ABC", (7..9), @{'name'="tom"; "age"=51}

Result

$a4[0] = 1, 2, 3
$a4[1] = "ABC"
$a4[2] = 7, 8, 9

宣告空陣列

$a5 = @()
$a5.Count -eq 0    # True

只有一個變數值,要設定為陣列

$a6 = , "Hello"
$a7 = @("Hello")
# 輸出 $a6 變數註標值為0的元素
$a6[0]
# 輸出 $a7 變數註標值為0的元素
$a7[0]
$a6.GetType()
$a7.GetType()

Result:

Hello
Hello

IsPublic IsSerial Name     BaseType                  
-------- -------- ----     --------                  
True     True     Object[] System.Array              
True     True     Object[] System.Array

反向取得陣列值

$array = "Tom", "Kate", "Dylan"
$array[-1]   # 陣列最後一個值
$array[-2]   # 陣列倒數第二個值

Result:

Dylan
Kate

計算陣列中符合條件值的數量(篩選等於 100,再計算 Count)

$a8 = @(100, 59, 95, 60, 100, 88, 76)
($a8 -eq 100).Count # 統計滿分人數

Array 元素的增加

雖然 Array 有 Add Method,但是當你使用 Add() 時,會出現"集合屬於固定大小"的錯誤
所以當你要在 Array 增加元素時,請使用數學運算 +

$a=@()
$a += 1
$a.GetType()
$a.Add(5)
$a += 5
$a

Result:

IsPublic IsSerial Name                                     BaseType                  
-------- -------- ----                                     --------                  
True     True     Object[]                                 System.Array              
以 "1" 引數呼叫 "Add" 時發生例外狀況: "集合屬於固定大小。"
位於 線路:4 字元:1
+ $a.Add(5)
+ ~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : NotSupportedException
 
1
5

雖然空一格,但不占空間

$a = @(1,2,3, ,5)
$a.count
$a

Result

4
1
2
3
5

Array 元素的減少

$a = @(1,2,3,4,5,6,7,8,9)
# 用註標來選擇
$a = $a[1,3,5]
$a

Result:

2
4
6

移除陣列中的空字串

# 宣告一個陣列
$a = @(1, 2, "", 4)
# 以下三種方法都可以移除空字串
$a = $a.Where( { $_ -ne ""} )
$a = $a | Where-Object { $_ -ne "" }
$a = $a -ne ""

Arrary 與 ArrayList 型態互換

$a = @()
$a.GetType()
# 將 [System.Array] 轉換成 [System.Collections.ArrayList]
$a = [System.Collections.ArrayList]$a
$a.GetType()
# [System.Collections.ArrayList] 轉換成 [System.Array]
$a = [System.Array]$a

Result:

IsPublic IsSerial Name       BaseType                  
-------- -------- ----       --------                  
True     True     Object[]   System.Array              
True     True     ArrayList  System.Object             

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

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 等待並取得執行結果

2021年7月27日 星期二

使用 Powershell 讀取系統事件檢視器的錯誤訊息及警告,然後輸出成 HTML 檔案

 Windows 的事件檢視器一直有許多人詬病,相當不好用!

光是大量的事件,就要等待半天;在加上沒辦法隨心所欲的設定關鍵字查詢

剛好前一陣子有人在問購買軟體來處理事件紀錄,個人是覺得很浪費錢。

因為現成軟體雖然方便,但是彈性很差。

所以就寫了個簡單的Powershell 程式給大家參考

2021年7月2日 星期五

使用 Powershell 發送 Synology Chat 訊息



Function Send-Chat{

    Param(

        [string] $Msg

    )

    Add-Type -AssemblyName System.Web

    $url = "http://your ip:port/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=2&token=%22el3fotmx3xHbiXGz17aTTXFUJ5DTzgun6bwhLA5bfqso%22"

    $json = 'payload={{"text": "{0}"}}' -f [System.Web.HttpUtility]::UrlEncode($Msg)

    Try{

        $Response = Invoke-WebRequest -Uri $url -Method POST -Body $json

    }Catch [exception]{

        $_

    }

}



Send-Chat ("{0:yyyy-MM-dd HH:mm:ss} DB Server CPU Loading {1:f0}%, Memory Loading {2:f0}%" -f (Get-Date), $CPUSensor, $MemSensor)

設定 $url 請參閱 https://kb.synology.com/zh-tw/DSM/tutorial/How_to_configure_webhooks_and_slash_commands_in_Chat_Integration#x_anchor_id5


要注意的是,當多台電腦,使用同一個 Webhook 傳送訊息時,只會處理第一個傳送到的
例如:我有 DB Server 1 & DB Server 2 跑相同的 Powershell 程式(控制同一時間同時傳送),就只會有其中一台的訊息會顯示

2021年3月2日 星期二

使用 Powershell 設定電腦環境

因為經常需要設定電腦,所以寫了個程式來設定電腦的環境,免得經常要手動設定,還容易忘記漏設定。

Param ([switch]$Verbose)
$VerboseStatus = $VerbosePreference
If($Verbose){
    $VerbosePreference = "Continue"
}
If(-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")){
    $ScriptFullPath = $MyInvocation.MyCommand.Definition
$arguments = "& '" + $ScriptFullPath + "'"
    # 另外以管理者身分執行 PowerShell,並載入 Script 來執行
Start-Process powershell -Verb runAs -ArgumentList $arguments
    # 中斷後面程式指令
Break
}
Write-Verbose "伺服器管理員不要每次開機都顯示"
New-ItemProperty -Path HKCU:\Software\Microsoft\ServerManager -Name DoNotOpenServerManagerAtLogon -PropertyType DWORD -Value “0x1” –Force | Out-Null
Write-Verbose "關閉 IE 增強安全性"
$AdminKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}"
Set-ItemProperty -Path $AdminKey -Name "IsInstalled" -Value 0
$UserKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}"
Set-ItemProperty -Path $UserKey -Name "IsInstalled" -Value 0
Stop-Process -Name Explorer
Write-Verbose "關閉使用者存取控制(UAC)"
Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "ConsentPromptBehaviorAdmin" -Value 00000000
Write-Verbose "設定微軟注音輸入法,預設為英文模式"
Set-ItemProperty "HKCU:\Software\Microsoft\IME\15.0\IMETC" -Name "Default Input Mode" -Value 0x00000001
Write-Verbose "設定電源模式-高效能"
& "powercfg.exe" /setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
Write-Verbose "設定電源模式-高效能-永不關閉螢幕"
& "powercfg.exe" -change -monitor-timeout-ac 0
Write-Verbose "開機自動登入系統"
$usrname = 'user'
$password = 'mypasswd'
$RegistryLocation = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
Set-ItemProperty $RegistryLocation -Name 'AutoAdminLogon' -Value '1'
Set-ItemProperty $RegistryLocation -Name 'DefaultUsername' -Value "$usrname"
Set-ItemProperty $RegistryLocation -Name 'DefaultPassword' -Value "$password"
Write-Verbose "設定時區、指定校時伺服器並校時"
$TimeZone = "Taipei Standard Time"
$NTPServer1 = "time1.google.com"
$NTPServer2 = "time.windows.com"
$NTPServer3 = "time.nist.gov"
Set-TimeZone -Id $TimeZone -PassThru | Out-Null
Push-Location
Set-Location HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DateTime\Servers
Set-ItemProperty . 0 $NTPServer1
Set-ItemProperty . 1 $NTPServer2
Set-ItemProperty . 2 $NTPServer3
Set-ItemProperty . "(Default)" "0"
Set-Location HKLM:\SYSTEM\CurrentControlSet\services\W32Time\Parameters
Set-ItemProperty . NtpServer $NTPServer1
Pop-Location
Stop-Service w32time
Start-Service w32time
Write-Verbose "變更網路區域為 Private (Not Public)"
$NetAdapter = (Get-NetConnectionProfile -IPv4Connectivity Internet).InterfaceIndex
Set-NetConnectionProfile -InterfaceIndex $NetAdapter -NetworkCategory Private
$VerbosePreference = $VerboseStatus
If($Host.Name -eq "ConsoleHost"){
    Pause
}

使用 PowerShell 清除暫存檔,並清除垃圾桶

先設定一個陣列 $tempfolders ,用來存放所有暫存檔路徑

然後使用 Remove-Item 指令來強制刪除檔案(force),包含子目錄(recurse)

因為有些暫存檔,有可能正在使用,是無法刪除的,會造成指令發出錯誤訊息,所以最後加上參數 -ErrorAction SilentlyContinue,當發生錯誤時,安靜地繼續執行指令

最後,清除垃圾桶,也是一樣的

$tempfolders = @("C:\Windows\Temp\*", "C:\Windows\Prefetch\*", "C:\Documents and Settings\*\Local Settings\temp\*", "C:\Users\*\Appdata\Local\Temp\*")

Remove-Item $tempfolders -force -recurse -ErrorAction SilentlyContinue

Clear-RecycleBin -Force -ErrorAction SilentlyContinue


如果要清除特別命名方式的檔案,或是不確定檔案位置的話,歡迎留言討論

使用 PowerShell 停用不安全的 SSL 2.0 3.0 & TLS 1.0 1.1 通訊協定

$Protocols = @("SSL 2.0", "SSL 3.0", "TLS 1.0", "TLS 1.1")
$EndPoints = @("Client", "Server")

Write-Verbose "停用 SSL 2.0 & 3.0 以及 TLS 1.0 & 1.1"

$Protocols | ForEach{
    $Protocol = $_
    If(!(Test-Path -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\$Protocol")){
        New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\$Protocol" | Out-Null
    }
    $EndPoints | ForEach{
        $EndPoint = $_
        If(!(Test-Path -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\$Protocol\$EndPoint")){
            New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\$Protocol\$EndPoint" | Out-Null
        }
        Switch($EndPoint){
            "Client"{
                Try{
                    Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\$Protocol\$EndPoint" -Name DisabledByDefault -ErrorAction Ignore
                    Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\$Protocol\$EndPoint" -Name DisabledByDefault -PropertyType DWORD -Value “0x1” –Force | Out-Null
                }Catch{
                    New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\$Protocol\$EndPoint" -Name DisabledByDefault -PropertyType DWORD -Value “0x1” –Force | Out-Null
                }
            }
            "Server"{
                Try{
                    Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\$Protocol\$EndPoint" -Name Enabled -ErrorAction Ignore
                    Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\$Protocol\$EndPoint" -Name Enabled -PropertyType DWORD -Value “0x0” –Force | Out-Null
                }Catch{
                    New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols\$Protocol\$EndPoint" -Name Enabled -PropertyType DWORD -Value “0x0” –Force | Out-Null
                }
            }
        }
    }
}

2021年2月18日 星期四

使用 PowerShell 進行硬體資產調查

Windows 的 WMI, CIM 有很多資訊可以運用管理
例如軟硬體資產、系統資源效能、程式運作等

這次要做的是硬體資產調查,以前我有介紹使用 AIDA64 Business 來做資產調查,但這套軟體是要付費的。

所以不想要花錢,又要做到資產調查,就只好自己動作來

只要將 PowerShell 程式將資料寫入網芳檔案,使用群組政策,讓使用者開機就自動執行這個 PowerShell ,資料就會自動寫入檔案

使用 PowerShell 讀取 Synology NAS LDAP Server

網路上查到的PowerShell 讀取 LDAP 資訊,幾乎清一色都是讀取 Active Directory....

很少講到如何讀取 Linux LDAP Server

找了好久,終於找到 C# 讀取 LDAP 的資訊,改成 PowerShell 

使用 PowerShell 批次執行 IE ,自動登入 Synology NAS

這次會想寫這個程式,是因為公司打算將 G Workspace 轉移到 Synology NAS

公司本來也沒有使用 AD or LDAP 做驗證,打算將帳號整合到 LDAP 做未來準備。

轉移郵件的功能 MailPlus 本身的功能就很完整,不需要太多操心

但是Synology Drive 卻沒辦法自動批次轉移全公司的 Google Drive!

使用Cloud Sync ,必須讓使用者自行操作,也沒辦法限制使用者只能加入指定的 Google Workspace

幸運的是,Synology Active Backup for G Suite可以將全部使用者的Google Drive 都備份下來,只要把使用者備份檔案複製到使用者家目錄,就可以正常在Synology Drive 使用。

只是因為整合了 LDAP 驗證,所以家目錄變成 "帳號-LDAP uidNumber" 這樣的格式。

手動去建立家目錄也很麻煩(一兩百個帳號),用PowerShell 讀取 LDAP 也有點麻煩,所以最簡單的方法就是使用者登入Synology NAS,Synology NAS 會自動建立家目錄。

以下不多說,就直接看程式吧

之前在每個 Page 變動後,都去偵測 $ie.ReadyState = 4 或是 $ie.Busy = $False 程式才繼續執行,都會發生錯誤

後來改成檢查 $ie.Document.getElementById('ext-gen84') 是否為物件也會誤判

再改成檢查 $ie.Document.getElementById('ext-gen84') 是否存在,也還是會誤判

最後改成判斷 $ie.Document.getElementById('ext-gen84')的 Type 才成功。

2020年12月3日 星期四

使用 PowerShell 對文字進行Escape編碼、Unescape解碼

 這個編碼解碼結果,與 JavaScript Escape() Unescape() 相同

 

$Original = 'Instead of going back to business as usual, Uber is taking this moment as an opportunity to reduce our environmental impact.The company said in a press release.[p]Uber 在新聞稿中表示:「Uber並沒有像往常一樣重返市場,而是以此為契機,減少對環境的影響。」'

$Encode = ""

$Alphabet = @(42,43,45,46,47,48,49,50,51,52,53,54,55,56,57,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,95,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122)

$Symbol = @(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,44,58,59,60,61,62,63,91,92,93,94,96,123,124,125,126,127)

# 編碼文字

for($i = 0; $i -lt $Original.Length; $i++){

    If($([int]$($Original[$i])) -in $Alphabet){

        $Encode += $Original[$i]

    }ElseIf($([int]$($Original[$i])) -in $Symbol){

        $Encode += [System.String]::Format("%{0:X2}", [int]$Original[$i])

    }Else{

        $Encode += [System.String]::Format("%u{0:X4}", [int]$Original[$i])

    }

}

Write-host $Encode

Write-Host "`r`n"

# 還原文字

Add-Type -AssemblyName System.Web

[System.Web.HttpUtility]::UrlDecode($Encode)