2025-08-20

Restrict unassigned users from logging into ChatGPT Team via Microsoft Entra SSO

ChatGPT Team support SSO with detailed setup instructions.
However, this version does not support SCIM provisioning.
To restrict unassigned users from logging into ChatGPT Team via SSO, we have an alternative option using Conditional Access to achieve this goal.

  • Navigate to the Microsoft Entra Admin Center and select your "ChatGPT WorkOS SSO" (using the name you specified) enterprise application.
  • Create a new policy in the "Conditional Access" of the "Security" section and name it "ChatGPT WorkOS SSO Deny All But Excluded Policy" (or any name you like).
  • Assign "All Users" and exclude the users or groups you want to allow to use the ChatGPT team.
  • Select "ChatGPT WorkOS SSO" as "Target resources" to include.
  • Grant Block access and turn Enable policy to "On"

That is all,
Now unassigned users may be able to log in but not access the ChatGPT team.

Setting up MFA for a Microsoft Entra shared account

Microsoft Entra admin center
=> Entra ID - Authentication methods 
=> Software OATH tokens
=> Set Include groups
(this step might not needed)

Browse "Security info" of Shared Account (https://aka.ms/mfasetup)
=> Add sign-in method
=> Choose Microsoft Authenticator
=> I want to use a different authenticator app
=> Finish the setup with OTP App you want to use (Microsoft Authenticator will do)

*. You may want to change the "Default sign-in method" from "Microsoft Authenticator - notification" to "App based authentication or hardware token - code" to avoid high App Notification frequency.

2025-08-13

Public DNS with Benefit

AdGuard DNS(無過濾功能):

94.140.14.140
94.140.14.141

AdGuard DNS(攔截廣告、跟蹤器和釣魚網站)

94.140.14.14
94.140.15.15

AdGuard DNS(攔截廣告、跟蹤器、釣魚網站和成人內容的網站)

94.140.14.15
94.140.15.16

Cloudflare DNS(隱私權保護)

1.1.1.1
1.0.0.1

Cloudflare DNS (阻止惡意程式)

1.1.1.2
1.0.0.2

Cloudflare DNS (阻止惡意軟件及成人內容)

1.1.1.3
1.0.0.3

2025-08-11

Split and Merge Sheng-Bo MailStore for SQL DB

It's not a popular Software / Application.
You may not need it.

First : SplitDB.vbs (Run on MailStore Server)
=> Split DB by day with SplitDB.vbs

Second: MergeDB.vbs  (Run on MailSearch Server)
=> Merge DB to Quarter

2025-07-30

Block Auth Failed IPs for Exchange

$UnBlockIPURL 是一個給 User 自行解鎖 IP 的清單,要另外處理
此處不提供與介紹

# in Hours
$BlockTime = 720;
$RuleGroupName = "ExchangeAuthFailBlockIPs"
$RuleSubnetsGroupName = "ExchangeAuthFailBlockSubnets"
$SMTPLookBackHours = 1;
$SMTPLogFullPath = "C:\Program Files\Microsoft\Exchange Server\V15\TransportRoles\Logs\FrontEnd\ProtocolLog\SmtpReceive\"
$SMTPLogSearchTailLines = 10000;
$UnBlockIPURL = 'https://www.contoso.com/BlockIPs/History/AllUnblock.Json';

$ClassBSubnetBlockCount = 3;

#-------------------------------------------------------------------------------------------------------------------------------------------

Export SharePoint Files with Path hierarchy

利用 Invoke-RestMethod 透過網址下載
需要具有 Site 管理權限

搭配 Junction 避免路徑過長的問題
如果還是過長,要再另外處理

注意非黑色文字的變數與執行細節

ExportSharePointSiteTriggler.ps1

$Finalize = $False;

$ConcurrentLimit = 5; 

Add-PSSnapin Microsoft.SharePoint.PowerShell
$Sites = (Get-SPSite -Limit ALL) 

$ExceptSites = @();
$ExceptSites += 'DefaultSite';

$TargetSites = @();
foreach ($SiteURL in ($Sites | where {(($_.Url -Split '/').count -eq 5) -and ($ExceptSites -notcontains ($_.Url -Split '/')[-1])})) {
$TargetSites += ($SiteURL.Url -Split '/')[-1];
};

$Location = 'S:\Command';
Set-Location -LiteralPath $Location;

foreach ($TargetSite in $TargetSites) {
write-host ('Processing ' + $TargetSite);
Start-Process "C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe" -ArgumentList "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", ($Location + '\ExportSharePointSiteMain.ps1'), "-TargetSite", $TargetSite

while ((Get-Process -Name 'PowerShell').Count -ge $ConcurrentLimit) {
Start-Sleep -Seconds 1;
};
};

2025-05-26

[PHP] Check if Client IP within CIDR

 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
    $ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
    $ip = $_SERVER['REMOTE_ADDR'];
};

if (cidr_match($ip,"192.168.0.0/16") == True) {
echo "hit";
} else {
echo "pass";
};

function cidr_match($ip, $range) {
    list ($subnet, $bits) = explode('/', $range);
    if ($bits === null) {
        $bits = 32;
    }
    $ip = ip2long($ip);
    $subnet = ip2long($subnet);
    $mask = -1 << (32 - $bits);
    $subnet &= $mask; # nb: in case the supplied subnet wasn't correctly aligned
    return ($ip & $mask) == $subnet;
};

Origin: https://stackoverflow.com/questions/594112/check-whether-or-not-a-cidr-subnet-contains-an-ip-address

2025-04-30

PHP 陣列 index 與 value 改小寫

$Array['INDEX'] = 'VALUE';

// index
$Array = array_change_key_case_recursive($Array ,CASE_LOWER);

// value
$Array = array_map('nestedLowercase', $Array );

=>

$Array['index'] = 'value';

2025-04-28

URL Parsing by PowerShell

URL Parsing by PowerShell

$URL = "https://api.contoso.com/level1/level2/Script.php?index=1&page=2&content=3";
$URI = [System.Uri]$URL;

Add-Type -AssemblyName System.Web;
$ParsedQueryString = [System.Web.HttpUtility]::ParseQueryString($URI.Query);
$QueryParams = New-Object -TypeName PSObject;
$i=0;
foreach($QueryStringObject in $ParsedQueryString){
    $QueryParams | Add-Member -MemberType NoteProperty -Name $QueryStringObject -Value $ParsedQueryString[$i];
    $i++;
};

$URI | fl;
$QueryParams.PSObject.Properties.Name;
$QueryParams;