diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..8eebe65
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*.ps1]
+end_of_line = lf
+indent_style = space
+indent_size = 4
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..6313b56
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto eol=lf
diff --git a/README.md b/README.md
index 8c7b314..babe033 100644
--- a/README.md
+++ b/README.md
@@ -22,24 +22,85 @@ New-Item -Path $ProfilePath -ItemType SymbolicLink -Value $(Resolve-Path profile
## Configuration
-Enable storing daily transcript:
+Some additional features can be turned on by setting their respective environment
+variables:
-```powershell
-Set-EnvironmentVariable -Key PROFILE_ENABLE_DAILYTRANSCRIPTS -Value 1
-```
+- `PROFILE_ENABLE_DAILY_TRANSCRIPTS`: Set this environment variable to `1` to
+ enable automatic transcript storing in `MyDocuments\Transcripts` (off by default.)
+
+- `PROFILE_LOAD_CUSTOM_SCRIPTS`: Declare a single path to dot-source Powershell
+ scripts from on profile launch.
+
+## Features
+
+
+Content
+
+### System Maintenance
+
+- `Update-Configuration`
+- `Update-System`
+
+### Utilities
+
+- `Get-Battery`
+- `Get-Calendar`
+- `Set-PowerState`
+- `Set-EnvironmentVariable`
+- `Get-EnvironmentVariable`
+- `Get-WorldClock`
+- `Remove-EnvironmentVariable`
+- `Start-DailyTranscript`
+- `Start-ElevatedConsole`
+- `Start-Timer`
+
+### Development
+
+- `Export-Branch`
+- `Get-NameOf`
+- `Get-ExecutionTime`
+- `Measure-ScriptBlock`
+- `New-DotnetProject`
+- `Stop-Work`
+
+### File Extensions
+
+- `Copy-FilePath`
+- `Export-Icon`
+- `Get-FileCount`
+- `Get-FileSize`
+- `Get-FilePath`
+- `Get-MaxPathLength`
+- `Remove-Directory`
+
+### Cryptography
+
+- `Get-Salt`
+- `Get-StringHash`
+- `Get-RandomPassword`
+
+### Miscellaneous
+
+- `Get-XCKD`
+
+### Enums
+
+- `OS`
+- `Month`
+
+
## Remarks
-Some optional external dependencies may be added over time, although they will never
-interfere with the core features of this profile.
+While this script attempts to be as lightweight as possible, a few externals are
+required to run some of the Cmdlets from this profile.
Additional Features
### Winfetch
-Creates an alias for [`winfetch`](https://github.com/kiedtl/winfetch) as a faster
-replacement for `neofetch` on Windows.
+Creates an alias for `neofetch` using https://github.com/kiedtl/winfetch on Windows.
```powershell
Install-Script -Name pwshfetch-test-1 -Scope CurrentUser
@@ -53,6 +114,7 @@ requires [`inkscape`](https://inkscape.org/) for the actual image conversion.
### Get-Calendar
Thin wrapper over Python's built-in `calendar` module to pretty print a calendar.
-Notice that this Cmdlet does *not* emit a PowerShell object.
+Notice that this Cmdlet does *not* emit a PowerShell object. The behavior of this
+Cmdlet is subject to future changes, see alo: [issue #9](https://github.com/StefanGreve/profile/issues/9).
diff --git a/profile.ps1 b/profile.ps1
index c8d1725..57572e6 100644
--- a/profile.ps1
+++ b/profile.ps1
@@ -1,4 +1,5 @@
using namespace System
+using namespace System.Collections.Generic
using namespace System.Diagnostics
using namespace System.Globalization
using namespace System.IO
@@ -14,8 +15,8 @@ using namespace Microsoft.PowerShell
$global:ProfileVersion = [PSCustomObject]@{
Major = 1
- Minor = 4
- Patch = 1
+ Minor = 5
+ Patch = 0
}
$global:OperatingSystem = if ([OperatingSystem]::IsWindows()) {
@@ -31,6 +32,12 @@ $global:OperatingSystem = if ([OperatingSystem]::IsWindows()) {
[CultureInfo]::CurrentCulture = "ja-JP"
$PSDefaultParameterValues["*:Encoding"] = "utf8"
+if ($env:PROFILE_LOAD_CUSTOM_SCRIPTS) {
+ Get-ChildItem -Path $env:PROFILE_LOAD_CUSTOM_SCRIPTS -Filter "*.ps1" | ForEach-Object {
+ . $_.FullName
+ }
+}
+
if ([OperatingSystem]::IsWindows()) {
$global:PSRC = "$HOME\Documents\PowerShell\profile.ps1"
$global:VSRC = "$env:APPDATA\Code\User\settings.json"
@@ -45,6 +52,10 @@ if ([OperatingSystem]::IsWindows()) {
$global:IsAdmin = ([Principal.WindowsPrincipal][Principal.WindowsIdentity]::GetCurrent()).IsInRole([Principal.WindowsBuiltInRole]::Administrator)
}
+if ([OperatingSystem]::IsLinux()) {
+ $global:IsAdmin = $(id -u) -eq 0
+}
+
$global:Desktop = [Environment]::GetFolderPath("Desktop")
$global:Documents = [Environment]::GetFolderPath("MyDocuments")
$global:Natural = { [Regex]::Replace($_.Name, '\d+', { $Args[0].Value.PadLeft(20) }) }
@@ -215,7 +226,18 @@ function Update-System {
}
if ($Applications.IsPresent || $All.IsPresent) {
- winget upgrade --all --silent
+ switch ($global:OperatingSystem) {
+ ([OS]::Windows) {
+ winget upgrade --all --silent
+ }
+ ([OS]::Linux) {
+ apt-get update
+ apt-get full-upgrade --yes
+ }
+ ([OS]::MacOS) {
+ brew upgrade
+ }
+ }
}
if ($Modules.IsPresent || $All.IsPresent) {
@@ -375,7 +397,7 @@ function Get-FileCount {
[Parameter(Position = 0, Mandatory, ValueFromPipeline)]
[string[]] $Path,
- [SearchOption] $SearchOption = [SearchOption]::TopDirectoryOnly
+ [SearchOption] $SearchOption = [SearchOption]::AllDirectories
)
process {
@@ -386,6 +408,33 @@ function Get-FileCount {
}
}
+function Remove-Directory {
+ [Alias("rd")]
+ [OutputType([void])]
+ [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
+ param(
+ [Parameter(Position = 0, ValueFromPipeline, Mandatory)]
+ [string[]] $Path
+ )
+
+ process {
+ foreach ($p in $Path) {
+ $Directory = [Path]::Combine($PWD.Path, $p)
+
+ if (![Directory]::Exists($Directory)) {
+ Write-Warning "Not a directory: $Directory"
+ continue
+ }
+
+ if ($PSCmdlet.ShouldProcess($Directory, "Remove $Path")) {
+ $SystemEntries = [Directory]::GetFileSystemEntries($Directory, "*.*", [SearchOption]::AllDirectories)
+ Remove-Item -Recurse -Force -Path $Directory
+ Write-Verbose "Removed $($SystemEntries.Count) file(s) in $Directory"
+ }
+ }
+ }
+}
+
function Copy-FilePath {
[Alias("copy")]
[OutputType([void])]
@@ -400,6 +449,31 @@ function Copy-FilePath {
}
}
+function Get-MaxPathLength {
+ process {
+ switch ($global:OperatingSystem) {
+ ([OS]::Windows) {
+ # On Windows, file names cannot exceed 256 bytes. Starting in Windows 10 (version 1607), the limit max
+ # path limit can be extended via setting this registry key to a value of 1 (property type: DWORD)
+ # https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry
+ $FileSystem = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled"
+ $MaxPathLength = $FileSystem.LongPathsEnabled -eq 1 ? 32767 : 260
+ Write-Output $MaxPathLength
+ }
+ ([OS]::Linux) {
+ # On virtually all file systems, file names are restricted to 255 bytes in length (cf. NAME_MAX).
+ # PATH_MAX equals 4096 bytes in Unix environments, though Unix can deal with longer file paths by using
+ # relative paths or symbolic links. To convert bytes to characters, you need to know the encoding ahead
+ # of time. For example, an ASCII or Unicode character in UTF-8 is 8 bits (1 byte), while a Unicode character
+ # in UTF-16 may take between 16 bits (2 bytes) and 32 bits (4 bytes) in memory, whereas UTF-32 encoded
+ # Unicode characters always require 32 bits (4 bytes) of memory
+ $MaxPathLength = getconf PATH_MAX /
+ Write-Output $MaxPathLength
+ }
+ }
+ }
+}
+
class Battery
{
[int] $ChargeRemaining
@@ -603,6 +677,7 @@ function Get-RandomPassword {
}
function New-DotnetProject {
+ [OutputType([void])]
param(
[Parameter(Mandatory)]
[string] $Name,
@@ -626,19 +701,19 @@ function New-DotnetProject {
Push-Location $OutputDirectory
}
process {
- dotnet new sln
dotnet new $Template --name $Name --language $Language --output $RootDirectory
dotnet new gitignore --output $OutputDirectory
dotnet new editorconfig --output $OutputDirectory
- dotnet sln add $Name
- dotnet restore $OutputDirectory
- dotnet build $OutputDirectory
+ dotnet restore $RootDirectory
+ dotnet build $RootDirectory
$Readme = New-Item -ItemType File -Name "README.md" -Path $OutputDirectory
Set-Content $Readme -Value "# $Name"
- $Packages | ForEach-Object {
- dotnet add $RootDirectory package $_
+ if ($PSBoundParameters.ContainsKey("Packages")) {
+ $Packages | ForEach-Object {
+ dotnet add $RootDirectory package $_
+ }
}
if ($InitRepository.IsPresent) {
@@ -647,7 +722,7 @@ function New-DotnetProject {
git commit -m "Init commit"
}
}
- end {
+ clean {
Pop-Location
}
}
@@ -876,12 +951,14 @@ function Set-EnvironmentVariable {
[string] $Value,
[Parameter(Position = 2)]
- [EnvironmentVariableTarget] $Scope = [EnvironmentVariableTarget]::User
+ [EnvironmentVariableTarget] $Scope = [EnvironmentVariableTarget]::Process,
+
+ [switch] $Override
)
- $Token = $global:OperatingSystem -eq [OS]::Windows ? ";" : ":"
+ $Token = [OperatingSystem]::IsWindows() ? ";" : ":"
- $OldValue = [Environment]::GetEnvironmentVariable($Key, $Scope)
+ $OldValue = $Override.IsPresent ? [string]::Empty : [Environment]::GetEnvironmentVariable($Key, $Scope)
$NewValue = $OldValue.Length ? [string]::Join($Token, $OldValue, $Value) : $Value
if ($PSCmdlet.ShouldProcess("Adding $Value to $Key", "Are you sure you want to add '$Value' to the environment variable '$Key'?", "Add '$Value' to '$Key'")) {
@@ -897,10 +974,10 @@ function Get-EnvironmentVariable {
[string] $Key = "PATH",
[Parameter(Position = 1)]
- [EnvironmentVariableTarget] $Scope = [EnvironmentVariableTarget]::User
+ [EnvironmentVariableTarget] $Scope = [EnvironmentVariableTarget]::Process
)
- $Token = $global:OperatingSystem -eq [OS]::Windows ? ";" : ":"
+ $Token = [OperatingSystem]::IsWindows() ? ";" : ":"
$EnvironmentVariables = [Environment]::GetEnvironmentVariable($Key, $Scope) -Split $Token
Write-Output $EnvironmentVariables
@@ -913,17 +990,25 @@ function Remove-EnvironmentVariable {
[Parameter(Position = 0, Mandatory)]
[string] $Key,
- [Parameter()]
+ [Parameter(Position = 1)]
[string] $Value,
- [EnvironmentVariableTarget] $Scope = [EnvironmentVariableTarget]::User
+ [EnvironmentVariableTarget] $Scope = [EnvironmentVariableTarget]::Process
)
- $Token = $global:OperatingSystem -eq [OS]::Windows ? ";" : ":"
+ $Token = [OperatingSystem]::IsWindows() ? ";" : ":"
- $RemoveValue = $Key -eq "PATH" ? $([Environment]::GetEnvironmentVariable("PATH", $Scope) -Split $Token | Where-Object { $_ -ne $Value }) -join $Token : $null
+ $Title = "Remove '$Value' from '$Key'"
+ $Description = "Are you sure that you want to remove '$Value' from the environment variable '$Key'?"
+ $RemoveValue = $([Environment]::GetEnvironmentVariable($Key, $Scope) -Split $Token | Where-Object { $_ -ne $Value }) -join $Token
+
+ if (!$PSBoundParameters.ContainsKey("Value")) {
+ $Title = "Remove all values in '$Key'"
+ $Description = "Are you sure that you want to remove the environment variable '$Key'?"
+ $RemoveValue = $null
+ }
- if ($PSCmdlet.ShouldProcess("Removing value '$Value' from environment variable '$Key'", "Are you sure you want to remove '$Value' from the environment variable '$Key'?", "Remove '$Value' from '$Key'")) {
+ if ($PSCmdlet.ShouldProcess($null, $Description, $Title)) {
[Environment]::SetEnvironmentVariable($Key, $RemoveValue, $Scope)
}
}
@@ -999,7 +1084,7 @@ function Measure-ScriptBlock {
begin {
$StopWatch = [Stopwatch]::new()
- $Measurements = New-Object System.Collections.Generic.List[System.TimeSpan]
+ $Measurements = New-Object List[System.TimeSpan]
if (![Stopwatch]::IsHighResolution) {
Write-Error -Message "Your hardware doesn't support the high resolution counter required to run this test" -Category DeviceError -ErrorAction Stop
@@ -1178,7 +1263,7 @@ function Start-DailyTranscript {
$Filename = [Path]::Combine($Transcripts, [string]::Format("{0}.txt", [datetime]::Now.ToString("yyyy-MM-dd")))
}
process {
- if ($env:PROFILE_ENABLE_DAILYTRANSCRIPTS -eq 1) {
+ if ($env:PROFILE_ENABLE_DAILY_TRANSCRIPTS -eq 1) {
Write-Verbose "Started a new transcript, output file is $Filename"
Start-Transcript -Path $Filename -Append -IncludeInvocationHeader -UseMinimalHeader | Out-Null
}
@@ -1190,7 +1275,7 @@ function Start-DailyTranscript {
function Get-ExecutionTime {
$History = Get-History
- $ExecTime = if ($History) { $History[-1].EndExecutionTime - $History[-1].StartExecutionTime } else { New-TimeSpan }
+ $ExecTime = $History ? ($History[-1].EndExecutionTime - $History[-1].StartExecutionTime) : (New-TimeSpan)
Write-Output $ExecTime
}
@@ -1201,7 +1286,7 @@ function Get-ExecutionTime {
$EnvironmentVariableKeyCompleter = {
param($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParameters)
- $Scope = $FakeBoundParameters.ContainsKey("Scope") ? $FakeBoundParameters.Scope : [EnvironmentVariableTarget]::User
+ $Scope = $FakeBoundParameters.ContainsKey("Scope") ? $FakeBoundParameters.Scope : [EnvironmentVariableTarget]::Process
[Environment]::GetEnvironmentVariables($Scope).Keys | ForEach-Object { [CompletionResult]::new($_) }
}