src/productivity.ps1

function Enable-Remoting {
  <#
  .SYNOPSIS
  Function to enable Powershell remoting for workgroup computer
  .PARAMETER TrustedHosts
  Comma-separated list of trusted host names
  example: 'RED,WHITE,BLUE'
  .EXAMPLE
  Enable-Remoting
  .EXAMPLE
  Enable-Remoting -TrustedHosts 'MARIO,LUIGI'
  #>

  [CmdletBinding()]
  Param(
    [String] $TrustedHosts = '*'
  )
  if (Test-Admin) {
    Write-Verbose '==> Making network private'
    Get-NetConnectionProfile | Set-NetConnectionProfile -NetworkCategory Private
    $Path = 'WSMan:\localhost\Client\TrustedHosts'
    Write-Verbose '==> Enabling Powershell remoting'
    Enable-PSRemoting -Force -SkipNetworkProfileCheck
    Write-Verbose '==> Updated trusted hosts'
    Set-Item $Path -Value $TrustedHosts -Force
    Get-Item $Path
  } else {
    Write-Error '==> Enable-Remoting requires Administrator privileges'
  }
}
function Find-Duplicate {
  <#
  .SYNOPSIS
  Helper function that calculates file hash values to find duplicate files recursively
  .EXAMPLE
  Find-Duplicate 'path/to/folder'
  .EXAMPLE
  Get-Location | Find-Duplicate
  #>

  [CmdletBinding()]
  Param(
    [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
    [String] $Name
  )
  Get-Item $Name |
    Get-ChildItem -Recurse |
    Get-FileHash |
    Group-Object -Property Hash |
    Where-Object Count -gt 1 |
    ForEach-Object { $_.Group | Select-Object Path, Hash } |
    Sort-Object -Property Hash |
    Write-Output
}
function Get-File {
  <#
  .SYNOPSIS
  Download a file from an internet endpoint (ex: http://example.com/file.txt)
  .EXAMPLE
  Get-File http://example.com/file.txt
  .EXAMPLE
  Get-File http://example.com/file.txt -File myfile.txt
  .EXAMPLE
  'http://example.com/file.txt' | Get-File
  #>

  [CmdletBinding()]
  Param(
    [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
    [String] $Url,
    [String] $File = 'download.txt'
  )
  $client = New-Object System.Net.WebClient
  $client.DownloadFile($Url, $File)
}
function Home {
  [CmdletBinding()]
  [Alias('~')]
  Param()
  Set-Location ~
}
function Install-SshServer {
  <#
  .SYNOPSIS
  Install OpenSSH server
  .LINK
  https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse
  #>

  [CmdletBinding(SupportsShouldProcess=$true)]
  Param()
  Write-Verbose '==> Enabling OpenSSH server'
  Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
  Write-Verbose '==> Starting sshd service'
  Start-Service sshd
  Write-Verbose '==> Setting sshd service to start automatically'
  Set-Service -Name sshd -StartupType 'Automatic'
  Write-Verbose '==> Adding firewall rule for sshd'
  New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
}
function Invoke-DockerInspectAddress {
  <#
  .SYNOPSIS
  Get IP address of Docker container at given name (or ID)
  .EXAMPLE
  dip <container name | id>
  .EXAMPLE
  echo <container name/id> | dip
  #>

  [CmdletBinding()]
  [Alias('dip')]
  Param(
    [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
    [String] $Name
  )
  docker inspect --format '{{ .NetworkSettings.IPAddress }}' $Name
}
function Invoke-DockerRemoveAll {
  <#
  .SYNOPSIS
  Remove ALL Docker containers
  .EXAMPLE
  dra <container name | id>
  #>

  [CmdletBinding()]
  [Alias('dra')]
  Param()
  docker stop $(docker ps -a -q); docker rm $(docker ps -a -q)
}
function Invoke-DockerRemoveAllImage {
  <#
  .SYNOPSIS
  Remove ALL Docker images
  .EXAMPLE
  drai <container name | id>
  #>

  [CmdletBinding()]
  [Alias('drai')]
  Param()
  docker rmi $(docker images -a -q)
}
function Invoke-GitCommand { git $args }
function Invoke-GitCommit { git commit -vam $args }
function Invoke-GitDiff { git diff $args }
function Invoke-GitPushMaster { git push origin master }
function Invoke-GitStatus { git status -sb }
function Invoke-GitRebase { git rebase -i $args }
function Invoke-GitLog { git log --oneline --decorate }
function Invoke-ListenForWord {
  <#
  .SYNOPSIS
  Start loop that listens for trigger words and execute passed functions when recognized
  .DESCRIPTION
  This function uses the Windows Speech Recognition. For best results, you should first improve speech recognition via Speech Recognition Voice Training.
  .EXAMPLE
  Invoke-Listen -Triggers 'hello' -Actions { Write-Color 'Welcome' -Green }
  .EXAMPLE
  Invoke-Listen -Triggers 'hello','quit' -Actions { say 'Welcome' | Out-Null; $true }, { say 'Goodbye' | Out-Null; $false }
 
  An action will stop listening when it returns a "falsy" value like $true or $null. Conversely, returning "truthy" values will continue the listening loop.
  #>

  [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Scope='Function')]
  [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'Continue')]
  [CmdletBinding()]
  [Alias('listenFor')]
  Param(
    [Parameter(Mandatory=$true)]
    [String[]] $Triggers,
    [ScriptBlock[]] $Actions,
    [Double] $Threshhold = 0.85
  )
  Use-Speech
  $Engine = Use-Grammar -Words $Triggers
  $Continue = $true;
  Write-Color 'Listening for trigger words...' -Cyan
  while ($Continue) {
    $Recognizer = $Engine.Recognize();
    $Confidence = $Recognizer.Confidence;
    $Text = $Recognizer.text;
    if ($Text.Length -gt 0) {
      Write-Verbose "==> Heard `"$Text`""
    }
    $Index = 0
    $Triggers | ForEach-Object {
      if ($Text -match $_ -and [Double]$Confidence -gt $Threshhold) {
        $Continue = & $Actions[$Index]
      }
      $Index++
    }
  }
}
function Invoke-RemoteCommand {
  <#
  .SYNOPSIS
  Execute script block on remote computer (like Invoke-Command, but remote)
  .EXAMPLE
  Invoke-RemoteCommand -ComputerNames PCNAME -Password 123456 { whoami }
  .EXAMPLE
  { whoami } | Invoke-RemoteCommand -ComputerNames PCNAME -Password 123456
  .EXAMPLE
  { whoami } | Invoke-RemoteCommand -ComputerNames PCNAME
 
  This will open a prompt for you to input your password
  .EXAMPLE
  { whoami } | irc -ComputerNames Larry, Moe, Curly
 
  Use the "irc" alias and execute commands on multiple computers!
  .EXAMPLE
  Get-Credential | Export-CliXml -Path .\crendential.xml
  { whoami } | Invoke-RemoteCommand -Credential (Import-Clixml -Path .\credential.xml) -ComputerNames PCNAME -Verbose
  #>

  [CmdletBinding()]
  [Alias('irc')]
  [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', 'Password')]
  [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '', Scope='Function')]
  [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Scope='Function')]
  Param(
    [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
    [ScriptBlock] $ScriptBlock,
    [Parameter(Mandatory=$true)]
    [String[]] $ComputerNames,
    [String] $Password,
    [PSObject] $Credential
  )
  $User = whoami
  if ($Credential) {
    Write-Verbose '==> Using -Credential for authentication'
    $Cred = $Credential
  } elseif ($Password) {
    Write-Verbose "==> Creating credential for $User using -Password"
    $Pass = ConvertTo-SecureString -String $Password -AsPlainText -Force
    $Cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $Pass
  } else {
    $Cred = Get-Credential -Message "Please provide password to access $(Join-StringsWithGrammar $ComputerNames)" -User $User
  }
  Write-Verbose "==> Running command on $(Join-StringsWithGrammar $ComputerNames)"
  Invoke-Command -ComputerName $ComputerNames -Credential $Cred -ScriptBlock $ScriptBlock
}
function Invoke-Speak {
  <#
  .SYNOPSIS
  Use Windows Speech Synthesizer to speak input text
  .EXAMPLE
  Invoke-Speak 'hello world'
  .EXAMPLE
  'hello world' | Invoke-Speak -Verbose
  .EXAMPLE
  1,2,3 | %{ say $_ }
  .EXAMPLE
  Get-Content .\phrases.csv | Invoke-Speak
  #>

  [CmdletBinding()]
  [Alias('say')]
  Param(
    [Parameter(Position=0, ValueFromPipeline=$true)]
    [String] $Text = '',
    [String] $InputType = 'text',
    [Int] $Rate = 0,
    [Switch] $Silent,
    [String] $Output = 'none'
  )
  Begin {
    Use-Speech
    $TotalText = ''
  }
  Process {
    if ($IsLinux -is [Bool] -and $IsLinux) {
      Write-Verbose '==> Invoke-Speak is only supported on Windows platform'
    } else {
      Write-Verbose '==> Creating speech synthesizer'
      $synthesizer = New-Object System.Speech.Synthesis.SpeechSynthesizer
      if (-not $Silent) {
        switch ($InputType)
        {
          'ssml' {
            Write-Verbose '==> Received SSML input'
            $synthesizer.SpeakSsml($Text)
          }
          Default {
            Write-Verbose "==> Speaking: $Text"
            $synthesizer.Rate = $Rate
            $synthesizer.Speak($Text)
          }
        }
      }
      $TotalText += "$Text "
    }
  }
  End {
    if ($IsLinux -is [Bool] -and $IsLinux) {
      Write-Verbose '==> Invoke-Speak was not executed, no output was created'
    } else {
      $TotalText = $TotalText.Trim()
      switch ($Output)
      {
        'file' {
          Write-Verbose '==> [UNDER CONSTRUCTION] save as .WAV file'
        }
        'ssml' {
          $Function:render = New-Template `
  '<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
      <voice xml:lang="en-US">
          <prosody rate="{{ rate }}">
              <p>{{ text }}</p>
          </prosody>
      </voice>
  </speak>'

          render @{ rate = $Rate; text = $TotalText } | Write-Output
        }
        'text' {
          Write-Output $TotalText
        }
        Default {
          Write-Verbose "==> $TotalText"
        }
      }
    }
  }
}
function New-DailyShutdownJob {
  <#
  .SYNOPSIS
  Create job to shutdown computer at a certain time every day
  .EXAMPLE
  New-DailyShutdownJob -At '22:00'
  #>

  [CmdletBinding()]
  Param(
    [Parameter(Mandatory=$true)]
    [String] $At
  )
  if (Test-Admin) {
    $Trigger = New-JobTrigger -Daily -At $At
    Register-ScheduledJob -Name 'DailyShutdown' -ScriptBlock { Stop-Computer -Force } -Trigger $Trigger
  } else {
    Write-Error '==> New-DailyShutdownJob requires Administrator privileges'
  }
}
function New-File {
  <#
  .SYNOPSIS
  Powershell equivalent of linux "touch" command (includes "touch" alias)
  .EXAMPLE
  New-File <file name>
  .EXAMPLE
  touch <file name>
  #>

  [CmdletBinding(SupportsShouldProcess=$true)]
  [Alias('touch')]
  Param(
    [Parameter(Mandatory=$true)]
    [String] $Name
  )
  if (Test-Path $Name) {
    (Get-ChildItem $Name).LastWriteTime = Get-Date
  } else {
    New-Item -Path . -Name $Name -ItemType 'file' -Value ''
  }
}
function New-ProxyCommand {
  <#
  .SYNOPSIS
  Create function template for proxy function
  .DESCRIPTION
  This function can be used to create a framework for a proxy function. If you want to create a proxy function for a command named Some-Command,
  you should pass "Some-Command" as the Name attribute - New-ProxyCommand -Name Some-Command
  .EXAMPLE
  New-ProxyCommand -Name 'Out-Default' | Out-File 'Out-Default.ps1'
  .EXAMPLE
  'Invoke-Item' | New-ProxyCommand | Out-File 'Invoke-Item-proxy.ps1'
  #>

  [CmdletBinding()]
  Param(
    [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
    [String] $Name
  )
  $metadata = New-Object System.Management.Automation.CommandMetadata (Get-Command $Name)
  Write-Output "
  function $Name
  {
    $([System.Management.Automation.ProxyCommand]::Create($metadata))
  }"

}
function New-SshKey {
  [CmdletBinding()]
  Param(
    [String] $Name = 'id_rsa'
  )
  Write-Verbose '==> Generating SSH key pair'
  $Path = "~/.ssh/$Name"
  ssh-keygen --% -q -b 4096 -t rsa -N '' -f TEMPORARY_FILE_NAME
  Move-Item -Path TEMPORARY_FILE_NAME -Destination $Path
  Move-Item -Path TEMPORARY_FILE_NAME.pub -Destination "$Path.pub"
  if (Test-Path "$Path.pub") {
    Write-Verbose "==> $Name SSH private key saved to $Path"
    Write-Verbose '==> Saving SSH public key to clipboard'
    Get-Content "$Path.pub" | Set-Clipboard
    Write-Output '==> Public key saved to clipboard'
  } else {
    Write-Error '==> Failed to create SSH key'
  }
}
function Open-Session {
  <#
  .SYNOPSIS
  Create interactive session with remote computer
  .PARAMETER NoEnter
  Create session(s) but do not enter a session
  .EXAMPLE
  Open-Session -ComputerNames PCNAME -Password 123456
  .EXAMPLE
  Open-Session -ComputerNames PCNAME
 
  This will open a prompt for you to input your password
  .EXAMPLE
  $Sessions = Open-Session -ComputerNames ServerA,ServerB
 
  This will open a password prompt and then display an interactive console menu to select ServerA or ServerB.
  $Sessions will point to an array of sessions for ServerA and ServerB and can be used to make new sessions:
 
  Enter-PSSession -Session $Sessions[1]
  #>

  [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', 'Password')]
  [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUsePSCredentialType', '', Scope='Function')]
  [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Scope='Function')]
  [CmdletBinding()]
  Param(
    [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
    [String[]] $ComputerNames,
    [String] $Password,
    [PSObject] $Credential,
    [Switch] $NoEnter
  )
  $User = whoami
  if ($Credential) {
    Write-Verbose '==> Using -Credential for authentication'
    $Cred = $Credential
  } elseif ($Password) {
    Write-Verbose "==> Creating credential for $User using -Password"
    $Pass = ConvertTo-SecureString -String $Password -AsPlainText -Force
    $Cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $Pass
  } else {
    $Cred = Get-Credential -Message "Please provide password to access $(Join-StringsWithGrammar $ComputerNames)" -User $User
  }
  Write-Verbose "==> Creating session on $(Join-StringsWithGrammar $ComputerNames)"
  $Session = New-PSSession -ComputerName $ComputerNames -Credential $Cred
  Write-Verbose '==> Entering session'
  if (-not $NoEnter) {
    if ($Session.Length -eq 1) {
      Enter-PSSession -Session $Session
    } else {
      Write-Label '{{#green Enter session?}}' -NewLine
      $Index = Invoke-Menu -Items $ComputerNames -ReturnIndex
      if ($null -ne $Index) {
        Enter-PSSession -Session $Session[$Index]
      }
    }
  }
  $Session
}
function Remove-DailyShutdownJob {
  <#
  .SYNOPSIS
  Remove job created with New-DailyShutdownJob
  .EXAMPLE
  Remove-DailyShutdownJob
  #>

  [CmdletBinding()]
  Param()
  if (Test-Admin) {
    Unregister-ScheduledJob -Name 'DailyShutdown'
  } else {
    Write-Error '==> Remove-DailyShutdownJob requires Administrator privileges'
  }
}
function Remove-DirectoryForce {
  <#
  .SYNOPSIS
  Powershell equivalent of linux "rm -frd"
  .EXAMPLE
  rf <folder name>
  #>

  [CmdletBinding(SupportsShouldProcess=$true)]
  [Alias('rf')]
  Param(
    [Parameter(Mandatory=$true)]
    [String] $Name
  )
  $Path = Join-Path (Get-Location) $Name
  if (Test-Path $Path) {
    $Cleaned = Resolve-Path $Path
    Write-Verbose "==> Deleting $Cleaned"
    Remove-Item -Path $Cleaned -Recurse
    Write-Verbose "==> Deleted $Cleaned"
  } else {
    Write-Error 'Bad input. No folders/files were deleted'
  }
}
function Take {
  <#
  .SYNOPSIS
  Powershell equivalent of oh-my-zsh take function
  .DESCRIPTION
  Using take will create a new directory and then enter the driectory
  .EXAMPLE
  take <folder name>
  #>

  [CmdletBinding(SupportsShouldProcess=$true)]
  Param(
    [Parameter(Mandatory=$true)]
    [String] $Name
  )
  $Path = Join-Path (Get-Location) $Name
  if (Test-Path $Path) {
    Write-Verbose "==> $Path exists"
    Write-Verbose "==> Entering $Path"
    Set-Location $Path
  } else {
    Write-Verbose "==> Creating $Path"
    mkdir $Path
    if (Test-Path $Path) {
      Write-Verbose "==> Entering $Path"
      Set-Location $Path
    }
  }
  Write-Verbose "==> pwd is $(Get-Location)"
}
function Test-Admin {
  <#
  .SYNOPSIS
  Helper function that returns true if user is in the "built-in" "admin" group, false otherwise
  .EXAMPLE
  Test-Admin
  #>

  [CmdletBinding()]
  [OutputType([Bool])]
  Param()
  if ($IsLinux -is [Bool] -and $IsLinux) {
    (whoami) -eq 'root'
  } else {
    ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) | Write-Output
  }
}
function Test-Empty {
  <#
  .SYNOPSIS
  Helper function that returns true if directory is empty, false otherwise
  .EXAMPLE
  echo <folder name> | Test-Empty
  .EXAMPLE
  dir . | %{Test-Empty $_.FullName}
  #>

  [CmdletBinding()]
  [ValidateNotNullorEmpty()]
  [OutputType([Bool])]
  Param(
    [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
    [String] $Name
  )
  Get-Item $Name | ForEach-Object { $_.psiscontainer -and $_.GetFileSystemInfos().Count -eq 0 } | Write-Output
}
function Test-Installed {
  [CmdletBinding()]
  [OutputType([Bool])]
  Param(
    [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
    [String] $Name
  )
  if (Get-Module -ListAvailable -Name $Name) {
    $true
  } else {
    $false
  }
}
function Use-Grammar {
  [CmdletBinding()]
  [OutputType([System.Speech.Recognition.SpeechRecognitionEngine])]
  Param(
    [Parameter(Mandatory=$true)]
    [String[]] $Words
  )
  Write-Verbose '==> Creating Speech Recognition Engine'
  $Engine = [System.Speech.Recognition.SpeechRecognitionEngine]::New();
  $Engine.InitialSilenceTimeout = 15
  $Engine.SetInputToDefaultAudioDevice();
  $Words | ForEach-Object {
    Write-Verbose "==> Loading grammar for $_"
    $Grammar = [System.Speech.Recognition.GrammarBuilder]::New();
    $Grammar.Append($_)
    $Engine.LoadGrammar($Grammar)
  }
  $Engine
}
function Use-Speech {
  [CmdletBinding()]
  Param()
  if ($IsLinux -is [Bool] -and $IsLinux) {
    Write-Verbose '==> Speech synthesizer can only be used on Windows platform'
  } else {
    $SpeechSynthesizerTypeName = 'System.Speech.Synthesis.SpeechSynthesizer'
    if (-not ($SpeechSynthesizerTypeName -as [Type])) {
      Write-Verbose '==> Adding System.Speech type'
      Add-Type -AssemblyName System.Speech
    } else {
      Write-Verbose '==> System.Speech is already loaded'
    }
  }
}