2024年9月17日 星期二

使用 PowerShell 生成自簽署證書

使用 PowerShell 生成自簽署證書

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

不過有以下兩個小缺點

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


# 1. 定義変数
$CNName = "PnP_Roks"
$OutPath = "D:\mycertificates"
$ExpiryYears = 10

# 2. 生成自簽署證書,存儲在當前用戶證書存儲區
if(-not (Test-Path $OutPath)) { mkdir $OutPath }
$cert = New-SelfSignedCertificate -Subject "CN=$CNName" -DnsName $CNName `
  -CertStoreLocation Cert:\CurrentUser\My `
  -NotAfter (Get-Date).AddYears($ExpiryYears).date
$cert
# 3. 將證書公鑰導出為 CER 檔案 (需上傳到應用程式中)
$cert |Export-Certificate -FilePath "$OutPath\$CNName.cer"


# 4. 將證書導出為 PFX 檔案 (選用)
$Pword = "YourPasswordHere"
$cert |Export-PfxCertificate -FilePath "$OutPath\$CNName.pfx" `
  -Password (ConvertTo-SecureString -String $Pword -Force -AsPlainText)

# 5. 從證書存儲區移除剛生成的證書 (選用)
$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





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 本來就要知道系統會擅自展開。

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