2024年9月17日 星期二

使用 PowerShell 一行指令自動生成自簽署證書

使用 PowerShell 一行指令自動生成自簽署證書


這是其他生成簽證的辦法

  1. 使用 openssl 自簽署SSL憑證證書
  2. 使用 powershell 自簽署SSL證書

第二個一樣用 powershell 但是是有簽署過程的,而本篇沒有簽署過程一行代碼直接完成全部,並自動儲存在 windwos 商店中,所以還需要多一個導出過程。





基於 SharePoint 的無應答登入模式改版後必須使用證書,這邊提供一個最簡證書生成方法,直接使用 PowerShell 5.1 內建的函式即可。

不過有以下兩個小缺點

  1. 無法匯出空白密碼的證書 pfx 檔案。(有這個需求用 OpenSSL 生成吧)
  2. 無法直接生成檔案,必須先存入 Windwos Certificate Store 匯出後再刪除


# 1. 定義変数
$CNName = "PnP_Roks"
$OutPath = "D:\mycertificates"
$ExpiryYears = 10
if(-not (Test-Path $OutPath)) { mkdir $OutPath }


# 2. 生成自簽署證書,存儲在當前用戶證書存儲區 (這一行就產好了)
$cert = New-SelfSignedCertificate -Subject "CN=$CNName" -DnsName $CNName `
  -CertStoreLocation Cert:\CurrentUser\My `
  -NotAfter (Get-Date).AddYears($ExpiryYears).date
$cert
# 3. 將證書安裝到受信任儲存區
$store = New-Object Security.Cryptography.X509Certificates.X509Store("Root", "LocalMachine")
$store.Open("ReadWrite"); $store.Add($cert); $store.Close()


# 4. 將證書公鑰導出為 CER 檔案 (需上傳到應用程式中)
$cert |Export-Certificate -FilePath "$OutPath\$CNName.cer"
# 5. 將證書導出為 PFX 檔案 (選用)
$Pword = "YourPasswordHere"
$cert |Export-PfxCertificate -FilePath "$OutPath\$CNName.pfx" `
  -Password (ConvertTo-SecureString -String $Pword -Force -AsPlainText)


# 6. 移除剛剛生成在個人儲存區的證書 (已經複製到信任區了這裡可以砍)
$cert | Remove-Item

這樣就生成完畢了,如果沒有移除證書的話可以在 certmgr.msc 中的 個人/憑證 區域找到該生成的證書。


需要上傳到 "應用程式" 中的只有 cer 證書而已,包含私鑰的 pfx 檔案不可以外洩。

比較理想的辦法是直接在目標伺服器生成證書,然後只匯出 cer 公鑰上傳到伺服器,一定程度避免私鑰產生副本。

環境允許的話甚至可以設置成不可匯出徹底銷毀,沒了再造一個就行。


上傳完畢接下來登入可以參考這篇 身份驗證 |PnP PowerShell




讀取證書檔案

其中如果要讀取 Store 中的 pfx 檔案可以這樣用

# 當前使用者
Get-Item -Path "Cert:\CurrentUser\My\$($cert.Thumbprint)"

# 本機
Get-Item -Path "Cert:\LocalMachine\My\$($cert.Thumbprint)"

對應的管理介面是

  • 當前用戶: certmgr.msc
  • 本機電腦: certlm.msc





匯出Linux格式

因為 PowerShell 不支援匯出 key 這個只能用第三方或是拿pwsh做了,這邊用 openssl 的範例從 pfx 轉換

導出 key 檔案

openssl pkcs12 -in "$CNName.pfx" -nocerts -nodes -out "$CNName.key"

導出 crt 檔案

openssl pkcs12 -in "$CNName.pfx" -clcerts -nokeys -out "$CNName.crt"

 

在 powershell 5.1 中似乎無法取出私鑰,即使用 Security.Cryptography.X509Certificates.X509Certificate2 取出來也是空值我就放棄了,改用 pwsh 或 openssl 吧

調查的結果好像是『 CNG (Cryptography Next Generation) vs CSP (CryptoAPI) 』的差異,PowerShell 5.1 只支援 CSP 缺了這個關鍵函式導致解不出來無法轉換




匯出Linux格式2

2025-07-16 這裡有個在微軟工作的大大寫的 pwsh 代碼可以直接產出
X509Certificate2 PowerShell module

產出來可以直接餵給 Nginx Proxy Manager 很方便

簡單的快速安裝腳本 (結尾的 chgs.com 是要註冊的網域)

& { param( [Parameter(Mandatory)][string]$cn, [switch]$force )

    # 檢查 PowerShell 版本
    if ($PSVersionTable.PSVersion.Major -lt 6) {
        Write-Error "❌ 此腳本需要 PowerShell 6.0 或更新版本。目前版本: $($PSVersionTable.PSVersion)" -EA Stop
    }

    # 檢查是否已有同 CN 的證書(比對 Subject)
    if (-not $force) {
        $store = New-Object Security.Cryptography.X509Certificates.X509Store("Root", "LocalMachine")
        $store.Open("ReadOnly")
        if ($store.Certificates | Where-Object { $_.Subject -eq "CN=$cn" }) {
            Write-Error "⚠ 已存在 CN=$cn 的證書,跳過安裝與匯出。" -EA Stop
        } $store.Close()
    }

    # 載入模組
    irm gist.githubusercontent.com/heaths/7b296f23e06156397bf2f07d69a64765/raw/841b2599ee45a096260454afdba89d21685c1ba0/X509Certificate2.psm1 | iex

    # 生成 SSL 證書
    $cert = New-X509Certificate2 -SubjectName "CN=$cn" -TLS -ValidDays 3650

    # 匯出 Linux 格式
    $cert | Export-X509Certificate2 "$cn.crt" -Type Certificate | Out-Null
    $cert | Export-X509Certificate2 "$cn.key" -Type PrivateKey | Out-Null

    # 安裝到信任區
    $store = New-Object Security.Cryptography.X509Certificates.X509Store("Root", "LocalMachine")
    $store.Open("ReadWrite"); $store.Add($cert); $store.Close()

    Write-Host "✅ 已成功產生並安裝 CN=$cn 的自簽憑證。"

} chgs.com





2024年9月13日 星期五

2024-09-09 SharePoint 禁用帳密登入改動 如何使用Application應用程式登入

2024-09-09 SharePoint 禁用帳密登入改動 如何使用Application應用程式登入

突然的改動導致原本使用帳密登入 SharePoint 的腳本變得無法執行了。

具體的錯誤信息是以下報錯 AADSTS50076 的信息

Connect-PnPOnline : AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a ne

w location, you must use multi-factor authentication to access '00000003-0000-0ff1-ce00-000000000000'. Trace ID: a28ff3

72-9f22-4018-9c6a-1341a7b16900 Correlation ID: 85a19fdf-03b2-4804-8987-49055663a3e5 Timestamp: 2024-09-13 03:44:03Z



官方的說明參考這篇
PnP Management Shell EntraID app is deleted : what should I do ? · Issue #4250

這邊有提到得的解是這篇
Register an Entra ID Application to use with PnP PowerShell | PnP PowerShell

不過這篇有幾個小插曲,那個英文分段分的不是很明確很容易誤以為整篇都在講同一個方法,其實總共有三個方法。

第二個坑是 $result = Register-PnPEntraIDApp -ApplicationName “PnP Rocks” -Tenant [yourtenant].onmicrosoft.com -OutPath c:\mycrtificates -DeviceLogin

這行代碼在 2.12 版本有 bug 是不能跑得,詳見這篇解法就是單純升級版本就修好了。


下面讓我們開始吧。




環境準備

要滿足兩個條件

  • PowerShell 7
  • Pnp.PowerShell 2.12.3

這是因為 Pnp.PowerShell 2 是面向PowerShell

下面請用管理員權限打開 PowerShell 5.1 執行

PowerShell 7 安裝指令
在 Windows 上安裝 PowerShell - PowerShell | Microsoft Learn

winget install --id Microsoft.Powershell --source winget

然後輸入 Pwsh 切換到 PowerShell 7 版本

pwsh

啟動之後檢查一下版本

$PSVersionTable

然後安裝 Pnp.PowerShell

Install-Module PnP.PowerShell -AllowPrerelease -Force -SkipPublisherCheck
Get-Module -Name PnP.PowerShell -ListAvailable

到這邊必要的環境就都安裝完成了




註冊應用程式

在開始前記得確保你打開的終端機是 PowerShell 7


最省力的辦法是用 PnP.PowerShell 社群幫你做的懶人包。
Register an Entra ID Application to use with PnP PowerShell | PnP PowerShell

這邊示範登入使用 CertificateBase64Encoded 所以沒有把證書存下來。
(事實上 CertificateBase64Encoded 就是證書檔案二進制,能還原的)

$result = Register-PnPEntraIDApp -ApplicationName PnP_Rocks -Tenant [你的租戶名].onmicrosoft.com -Interactive

預設的租戶名 xxx.onmicrosoft.com
方法1:從你的網站 https://[你的租戶名].sharepoint.com 網址上找到。
方法2:組織通常會新增自己的域名比如說 xxx@gmail.com 中的 gmail.com 也可以用。沒新增的話預設是 xxx@xxx.onmicrosoft.com 兩個長一個樣。

 

執行之後會出現內部的.NET的UI登入介面,請注意必須使用具網域管理員權限的帳號登入,總共會要求登入兩次

  • 第一次是創建應用程式。(這次其實不用權限)
  • 第二次是讓網域管理員承認應用程式權限

完成之後可以通過查看 $result 獲取證書的 Base64Encoded

$result

記得將他保存下來

$result > C:\PnP_Rocks_Certificate.txt

然後我們利用這個資訊登入

Connect-PnPOnline 'https://[你的租戶名].sharepoint.com/sites/[你的網站名]' -ClientId $result."AzureAppId/ClientId" -Tenant [你的租戶名].onmicrosoft.com -CertificateBase64Encoded $result.Base64Encoded

到這邊就登入成功了

切記這個權限超級超級大,Base64Encoded內容一定要保護好。


創建完成之後可以在這個網址找到自動建立的應用程式


在這裡可以編輯權限


 (這邊截圖不是照上面來的參考用而已,上面設定完權限不應是委託而是應用程式)




註冊應用程式2 (儲存證書)

重複的就不講了留最重要的代碼就好,這是會把證書儲存下來的方法。

mkdir d:\mycertificates
$result = Register-PnPEntraIDApp -ApplicationName PnP_Rocks -Tenant [你的租戶名].onmicrosoft.com -OutPath d:\mycertificates -Interactive

證書輸出之後把 pfx 安裝到”個人”,然後用下面的方式登入



Connect-PnPOnline 'https://[你的網站].sharepoint.com/sites/Maple' -ClientId $result."AzureAppId/ClientId" -Tenant [你的租戶名].onmicrosoft.com -Thumbprint $result."Certificate Thumbprint"

總之把他設定成不可匯出就不用被幹走了,剩下的證書封存的方式在自己琢磨一下。





參考網站

  1. 身份驗證 |PnP PowerShell
  2. [BUG] Register-PnPEntraIDApp Hanging · Issue #4260 · pnp/powershell (github.com)
  3. PnP Management Shell EntraID app is deleted : what should I do ? · Issue #4250 · pnp/powershell (github.com)



2024年8月31日 星期六

遠端桌面 無法使用 Microsoft 帳戶登入

遠端桌面 無法使用 Microsoft 帳戶登入

這個應該是Bug,提供一個比較萬解的辦法可以更新本地密碼的指令

複製底下的指令

runas /u:YourMail@gmail.com cmd.exe

然後打開終端機貼上並把 YourMail@gmail.com 這個部分改成你自己的帳號
然後正常輸入密碼登入一次,就會順便更新你的密碼了

會造成這個局面有幾種情況

  1. 更改過密碼
  2. 原始是家用板透過市集或序號升級到進階板
  3. 創建用戶的時候不是直接創建用戶,而是先創建離線用戶後再登入線上帳號

似乎只有直接使用微軟帳號創建Windwos用戶才會順便登陸密碼,不然就都會卡住原因不詳應該是bug。


如果是因為改密碼燈不進去,一個辦法是使用本機帳號登入,離線用戶預設是帳密前五碼,以前面的例子來說

YourMail@gmail.com 這個帳號的本機用戶是 yourm,使用 yourm 與舊密碼還是可以登入的。當然如果是3的情況要查一離線帳號是什麼,不會是預設的前五碼。




其他推薦文章

Rdp遠端桌面連線 降低延並遲提高禎數到 60fps



參考

解決 Microsoft 帳戶無法登入遠端桌面 | Then Notes 隨筆

2024年7月31日 星期三

移除 Visual Studio 桌面右鍵選單

移除 Visual Studio 2022 桌面和資料夾的右鍵選單

自行打開終端機執行下面指令

reg add "HKEY_CLASSES_ROOT\Directory\Background\shell\AnyCode" /v "LegacyDisable" /t REG_SZ /d "" /f
reg add "HKEY_CLASSES_ROOT\Directory\shell\AnyCode" /v "LegacyDisable" /t REG_SZ /d "" /f

執行好之後重啟 explorer 就會消失了,不知道怎麼重啟直接重開機也行。



對應的登陸檔長這個樣子

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\Background\shell\AnyCode]
"LegacyDisable"=""

[HKEY_CLASSES_ROOT\Directory\shell\AnyCode]
"LegacyDisable"=""



2024年6月11日 星期二

python click 參數中自訂自己的檢查類 type

python click 參數中自訂自己的檢查類 type

原生函式其實已經非常足夠了,只是如果專案比較刁鑽一點又不想把檢查函式寫在 command 函式中,而是傳入之前檢查可以自訂類來做

目標先挑一個你覺得有用的免得要重寫太多功能,這邊假設是路徑就挑 click.Path 這個類別繼承,然後在此之上加上自己的功能

# 自訂 type 類別 ExtPath 用例
class ExtPath(click.Path):
    def __init__(self, my_check=False, **kwargs):
        self.my_check = my_check
        super().__init__(**kwargs)

    def convert(self, value, param, ctx):
        print(f"ExtPath.convert():: 啟動此函數的對象參數是 {param.name} 目前值為 {value}, 自訂參數 my_check={self.my_check}")
        return super().convert(value, param, ctx)

# 群組命令3:: demo2
@entry.command()
@click.argument('name', required=True, nargs=1, type=ExtPath())
@click.argument('path', required=True, nargs=-1, type=ExtPath(my_check=True, exists=True))
def demo2(name, path):
    cli.echo_inf_message(f"[\n    name: {name}\n    path: {path}\n]")
    """獲取從cli設置的初始化信息"""

大致上會用到的部分都寫上了,自己參考著應該就能知道怎麼使用了


範例用例

(.venv) PS C:\Users\user\OneDrive\Git Repository\PyCli_Template> myapp demo2 Test setup.py *.ps1     
ExtPath.convert():: 啟動此函數的對象參數是 name 目前值為 Test, 自訂參數 my_check=False
ExtPath.convert():: 啟動此函數的對象參數是 path 目前值為 setup.py, 自訂參數 my_check=True
ExtPath.convert():: 啟動此函數的對象參數是 path 目前值為 deploy.ps1, 自訂參數 my_check=True
[
    name: Test
    path: ('setup.py', 'deploy.ps1')
]


需要講的大概就是順序了,這個類的順序在參數擴展之後的 for 迴圈之內,就算是附加在 nargs=-1 裡面也會自動跑多次,可以從範例中看到。

這裡處理完畢之後就會正式進入 comannd 函式了,可以在這裡修改變數值但不是很建議,可能會跟官方功能有衝突。





2024年6月7日 星期五

python 裝飾器 時間計時範例

python 裝飾器 時間計時範例

來自高天的講解中複製出來的代碼
【python】装饰器超详细教学,用尽毕生所学给你解释清楚,以后再也不迷茫了!

import time

def timeit(iteration):

    def inner(f):

        def wrapper(*args, **kwargs):
            start = time.time()
            for _ in range(iteration):
                ret = f(*args, **kwargs)
            print(time.time() - start)
            return ret

        return wrapper

    return inner

@timeit(1000)
def double(x):
    return x * 2

double(2)

這個例子很關鍵的地方在於事後忘記了看一眼就馬上明白了

怎麼理解最快,這個最快的辦法就是不要理解

首先第一個問號區 *args, **kwargs ,只需要記住一件事情這整個區塊叫做 完美轉發 我不管前面的人給我什麼反正我就是直接複製對方的參數設定值

剩下的直接都當作一個區塊記下來就好了,只需要知道從 1000 那個位置可以設置 for 迴圈的變數,然後在 ret 的位置就是執行原本的函式

在 wrapper 中你可以對這個函式的前跟後動手,你希望在函式執行前先弄點什麼就寫在 ret 之前,你希望在執行後再弄點什麼就寫在 ret 之後

而這個對函式前後動手的行為就是裝飾器中的一個核心價值了,只需要透過追加一行就可以包裝函式前後的動作了




如果要放在類裡面參考這篇

【python】如何在class内部定义一个装饰器?这里的坑你要么不知道,要么不会填! (youtube.com)


2024年6月6日 星期四

python click 如何避免參數中的通配符被展開

python click 如何避免參數中的通配符被展開

在這篇討論中有被提到
Expand globs in arguments on Windows? · Issue #1096 · pallets/click (github.com)

click預設會通過 glob 展開通配符這是為了確保在 Windows 或 Linux 上能獲得一致的效果
不過反過來說如果你就是要傳入通配符,其實沒有任何手段可以避免的,原作似乎就沒開放了

討論的最後有提到改進在這裡
expand patterns in Windows args by davidism · Pull Request #1830 · pallets/click (github.com)

那就沒辦法只能硬幹了,就把這代碼幹掉

import click

# 定義一個新的 _expand_args 函數,直接返回原始參數
def _no_expand_args(args):
    return args
# 猴子補丁覆蓋 Click 的 _expand_args 方法
from click import core
core._expand_args = _no_expand_args

@click.command()
@click.argument('path', type=str)
def process_path(path):
    click.echo(f"Received path: {path}")

if __name__ == '__main__':
    process_path()


這樣一來就不會被騷擾了,反過來說你也可以在這裡寫上屬於自己的條件

上面是簡寫完整一點的整個函式其實是長這樣的

# 定義一個新的 _expand_args 函數,直接返回原始參數
import typing as t
def _no_expand_args(
    args: t.Iterable[str],
    *,
    user: bool = True,
    env: bool = True,
    glob_recursive: bool = True,
) -> t.List[str]:
    return list(args)
# 猴子補丁覆蓋 Click 的 _expand_args 方法
from click import core
core._expand_args = _no_expand_args


原始代碼可以在這裡看,可以參考是如何用 glob 展開變數的
click/src/click/utils.py at 923d197b56caa9ffea21edeef5baf1816585b099 · pallets/click (github.com)



補充說明一下流程,我覺得 click 原作這個解不夠漂亮,但確實已經是沒有辦法中的一個還算可以的辦法了。

原作的做法是收到來自命令的參數時,就先進行展開,不管是 option 還是 augument 都會被展開,這個有個問題是

在 linux 上 bash 會直接展開後才輸入,所以實際 click 拿到的參數已經是展開後的,看起來很美好的統一了 Win 和 linux,其實背後是犧牲了一個特性

如果想試圖傳遞通配符給程序,通常的做法是在 bash 中輸入雙引號,以便告訴 bash 不要展開這個字串直接輸入到程序中

問題就在這裡,click拿到參數之後不管如何總之每個都先過一輪 glob() 就對了,所以最終還是會被展開了,儘管已經用雙引號

好處就是統一了 Windwos 跟 Linux 的邏輯,背後是犧牲透過雙引號輸入通配符號的可能性, Click 強制 glob() 導致無論如何參數都會被展開




不過其實有邏輯漏洞可以卡,透過這個方式 `myapp -p"*.py"` 或是全寫也行,這樣過 glob() 的時時候由於 `-p"*.py"` 不是路徑就不會被展開了

如果 -p 後面有空格,那就會被切開來分兩次進glob() 還是會被展開,類似技巧還有別的組合可以實現


繼續回來原本的話題,這個要比較合理的解決,估計還是只能開放 glob 的自由度吧,預設不要開,自己決定要不要展開,而且最好還是可以決定系統層級的 `glob_win=Ture`, `glob_unix=Ture`。

這樣至少 linux 上還可以保持透過雙引號控制要不要展開,而Windwos這沒辦法了終端機本來就不會展開了,不管雙不雙引號都一樣。

兩邊系統的統一性我覺得不是那個重要,對於 win 本來就要知道系統不會幫你展開,對於 unix 本來就要知道系統會擅自展開。

函式只需要提供一個快捷讓你自己決定要不要賭這個洞。