ShanghaiTechCommon.ps1
# Set-StrictMode -Version Latest $Script:Config = @{ HeartbeatMinutes = 2 LoginRetry = 4 DnsServer = "10.15.44.11" TestUri = "http://223.5.5.5" InternalProbeHost = "software.lib.shanghaitech.edu.cn" } enum LogLevel { Debug = 0 Info = 1 Success = 2 Warning = 3 Error = 4 Fatal = 5 } <# .SYNOPSIS Write log message to console .EXAMPLE Write-ShanghaiTechLog -Message a -Level Success #> function Write-ShanghaiTechLog { param ( # Log message [Parameter(Mandatory)] [string] $Message, # Log level [LogLevel] $Level = ([LogLevel]::Info), # Leading string [string] $Char = '', # Foreground color [System.ConsoleColor] $Color ) $display = switch ($Level) { ([LogLevel]::Debug) { @{Char = "i"; Color = [System.ConsoleColor]::Blue } } ([LogLevel]::Info) { @{Char = "·"; Color = [System.ConsoleColor]::Cyan } } ([LogLevel]::Success) { @{Char = "●"; Color = [System.ConsoleColor]::Green } } ([LogLevel]::Warning) { @{Char = "!"; Color = [System.ConsoleColor]::Yellow } } ([LogLevel]::Error) { @{Char = "●"; Color = [System.ConsoleColor]::Red } } ([LogLevel]::Fatal) { @{Char = "×"; Color = [System.ConsoleColor]::Red } } } if ($Char.Length -eq 0) { $Char = $display.Char } if ($null -eq $Color) { $Color = $display.Color } Write-Host $Char -ForegroundColor $Color -NoNewline Write-Host " " -NoNewline Write-Host ([datetime]::Now.ToShortDateString()) -ForegroundColor $Color -NoNewline Write-Host " " -NoNewline Write-Host ([datetime]::Now.ToLongTimeString()) -ForegroundColor $Color -NoNewline Write-Host "`t" -NoNewline Write-Host $Message } <# .SYNOPSIS Return true if the host is connected to ShanghaiTech network .DESCRIPTION Checking if hostname `software.lib.shanghaitech.edu.cn` can be resolved by 10.15.44.11. #> function Test-ShanghaiTechNetwork { [CmdletBinding()] [OutputType([bool])] param() # TODO if ($Configuration.Count -gt 0) { try { $null = Resolve-DnsName $Script:Config.InternalProbeHost -Server $Script:Config.DnsServer -ErrorAction Stop } catch [System.ComponentModel.Win32Exception] { return $false } return $true } return $false } function Get-ShanghaiTechLoginResponse { [CmdletBinding()] param() try { $response = Invoke-WebRequest -Uri $Config.TestUri -MaximumRedirection 0 -ErrorAction Stop } catch [System.Net.Http.HttpRequestException] { $response = $_.Exception.Response } catch { $response = $_.Exception.Response } if ($response.StatusCode -eq [System.Net.HttpStatusCode]::Redirect -and $response.Headers.Location.Host.EndsWith("shanghaitech.edu.cn")) { $response } } function Get-ShanghaiTechLoginParams { [CmdletBinding()] [OutputType([hashtable])] param() $response = Get-ShanghaiTechLoginResponse if (-not $response) { return } $location = $response.Headers.Location if (-not $location) { return } $query = [System.Web.HttpUtility]::ParseQueryString($location.Query) $hashtable = @{} foreach ($key in $query.AllKeys) { $hashtable[$key] = $query[$key] } $hashtable } <# .SYNOPSIS Return true if need to login to ShanghaiTech network .DESCRIPTION If we can connect to http://example.com without being redirected to a portal, we don't need to login. #> function Test-ShanghaiTechLogin { [CmdletBinding()] [OutputType([bool])] param() if (Get-ShanghaiTechLoginResponse) { return $true } return $false } <# .SYNOPSIS Send heartbeats to ShanghaiTech wifi controller #> function Start-ShanghaiTechHeartbeat { [CmdletBinding()] param( [int] $Interval = 120, [int] $FailMax = 5 ) $FailCount = 0 while ($true) { if ($FailCount -gt $FailMax) { Write-ShanghaiTechLog -Level ([LogLevel]::Error) "Failed $FailMax times, stop heartbeat." return $false } $HeartbeatSuccess = -not (Test-ShanghaiTechLogin) if (-not $HeartbeatSuccess) { # $Response = $_.Exception.Response $FailCount++ $Retry = [math]::Pow(2, $FailCount + 2) Write-ShanghaiTechLog -Level ([LogLevel]::Warning) "Heartbeat failed." Start-Sleep -Seconds $Retry continue } if ($HeartbeatSuccess) { $FailCount = 0 Write-ShanghaiTechLog -Level ([LogLevel]::Info) "Heartbeat successful." } else { $FailCount++ $Retry = [math]::Pow(2, $FailCount) Write-ShanghaiTechLog -Level ([LogLevel]::Warning) "Heartbeat failed, retry in $Retry seconds." Start-Sleep -Seconds $Retry continue } Start-Sleep -Seconds $Interval } } #region Config <# .SYNOPSIS Get config directory for storing credentials #> function Get-ShanghaiTechConfigDirectory { [CmdletBinding()] param() if ($env:XDG_CONFIG_HOME) { $Directory = $env:XDG_CONFIG_HOME } elseif ($env:LOCALAPPDATA) { $Directory = $env:LOCALAPPDATA } elseif ($env:USERPROFILE) { $Directory = $env:USERPROFILE } elseif ($env:HOME) { $Directory = $HOME } else { $Directory = Join-Path (Resolve-Path ~) ".config" } if (-not (Test-Path $Directory)) { $Directory = New-Item -ItemType Directory $Directory } $Directory = Join-Path $Directory "stulogin" if (-not (Test-Path $Directory)) { # If old path exists and new one does not exist, copy it $OldDirectory = Join-Path (Resolve-Path ~) ".stulogin" if (Test-Path $OldDirectory) { Copy-Item -Recurse $OldDirectory $Directory } else { $null = New-Item -ItemType Directory $Directory } } $Directory } <# .SYNOPSIS Get config file for storing credentials #> function Get-ShanghaiTechConfigFile { [CmdletBinding()] param( # Directory to store credential, default to $HOME/.config/stulogin [Parameter( ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true, HelpMessage = 'The directory to save .stulogin file. The file name will be credential_$([Environment]::UserName)_$([Environment]::MachineName).xml')] [Alias('dir')] [string] $Directory = (Get-ShanghaiTechConfigDirectory), # Resolve path [switch] $Resolve = $false ) $fileName = "credential_$([Environment]::UserName)_$([Environment]::MachineName).xml" Join-Path $Directory $fileName -Resolve:$Resolve } #endregion #region Credential <# .SYNOPSIS Export ShanghaiTech login credential to file .NOTES This function is meant to be used in a pipeline #> function Export-ShanghaiTechStore { [CmdletBinding()] param ( # Credential to export [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('cred')] [ValidateNotNull()] [pscredential] [System.Management.Automation.Credential()] $Credential, # Session with cookies to export [Parameter(ValueFromPipelineByPropertyName = $true)] [Microsoft.PowerShell.Commands.WebRequestSession] $Session, # Directory to store credential, default to $HOME/.config/stulogin [Parameter(ValueFromPipelineByPropertyName = $true, HelpMessage = 'The directory to save .stulogin file. The file name will be credential_$([Environment]::UserName)_$([Environment]::MachineName).xml')] [Alias('dir')] [string] $Directory = (Get-ShanghaiTechConfigDirectory) ) $sessionId = if ($Session) { $id = $session.Cookies.GetCookies('https://net-auth.shanghaitech.edu.cn').Item('PSESSIONID') if ($id) { $id.Value } } else {} $ExportPath = Get-ShanghaiTechConfigFile -Directory $Directory Export-Clixml -InputObject @{ Credential = $Credential; SessionId = $sessionId } -Path $ExportPath } <# .SYNOPSIS Test if ShanghaiTech login credential exists #> function Test-ShanghaiTechStore { [CmdletBinding()] param ( [Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Path to the credential.")] [ValidateNotNullOrEmpty()] [Alias('dir')] [string] $Directory = (Get-ShanghaiTechConfigDirectory) ) return (Test-Path (Get-ShanghaiTechConfigFile -Directory $Directory)) } <# .SYNOPSIS Import ShanghaiTech login credential and session from file #> function Import-ShanghaiTechStore { [CmdletBinding()] [OutputType([hashtable])] param ( [Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Path to the credential.")] [ValidateNotNullOrEmpty()] [Alias('dir')] [string] $Directory = (Get-ShanghaiTechConfigDirectory) ) $path = Get-ShanghaiTechConfigFile -Directory $Directory -Resolve $store = Import-Clixml -Path $path $result = @{} if ($store -is [pscredential]) { $result.Credential = $store } elseif ($store -is [hashtable]) { $result.Credential = $store.Item('Credential') if ($store.Item('SessionId')) { $session = [Microsoft.PowerShell.Commands.WebRequestSession]::new() $session.Cookies.Add([System.Net.Cookie]::new("PSESSIONID", $store.SessionId, "/", "net-auth.shanghaitech.edu.cn")) $result.Session = $session } } return $result } <# .SYNOPSIS Remove ShanghaiTech login credential from file #> function Remove-ShanghaiTechStore { [CmdletBinding()] param ( [Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Path to the credential.")] [ValidateNotNullOrEmpty()] [Alias('dir')] [string[]] $Directory = (Get-ShanghaiTechConfigDirectory), # Force [switch] $Force ) # Delete old directory if exists $OldPath = Join-Path ~ .stulogin if (Test-Path $OldPath) { Remove-Item -Recurse $OldPath } $Path = Get-ShanghaiTechConfigFile -Directory $Directory -Resolve $Remove = Remove-Item $Path -Force:$Force return $Remove } #endregion |