Ssh.ps1

function Initialize-Ssh {

  if (-Not(Get-Command "ssh" -ErrorAction SilentlyContinue)) {
    Install-Scoop 
    scoop install ssh
  }

}

function Connect-Ssh {
  param(
    [Parameter(mandatory = $True)]
    [string] $Ip,
    [String] $Command
  )

  Initialize-Ssh

  # if copy-key
  # scp -o UserKnownHostsFile=/dev/null -o "StrictHostKeyChecking=no " $KEY_PATH $host:/home/box/.ssh/id_ed25519
  # ssh -t $host "sed -i 's/\r//' .ssh/id_ed25519 && chmod 600 .ssh/id_ed25519"

  Write-Host "Connecting to host ${Ip} port 22 ..."
  do {
    try {
      ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR root@$Ip $Command
    }
    catch {}
  } while (-Not $Success)
}

function Get-SshConfigFile {
  "${env:USERPROFILE}\.ssh\config"
}

function Get-SshKey {
  param(
    [string] $Type = "ed25519", # or rsa
    [string] $Name,
    [switch] $Public
  )

  Initialize-Ssh

  if (-not(Test-Path -Path $HOME/.ssh -PathType Container)) {
    New-Item -Path $HOME/.ssh -ItemType Directory | Out-Null
  }

  $keyPath = "$global:HOME/.ssh/id_${Type}_box"
  if (-not(Test-Path -Path $keyPath -PathType Leaf)) {
    Write-Host("Creando clave ssh '${keyPath}'")
    # No podem crear una clau sense contrasenya sense preguntar a l'usuari fent servir la opció -N '' perquè a vegades ha de ser '""', però a vegades ha de ser 'xxxxx' mínim 5 caracters i no admet '""'.
    if ($Type -Eq "ed25519") {
      ssh-keygen -t ed25519 -f ${keyPath} | Out-Null
    }
    else {
      ssh-keygen -m PEM -t rsa -b 4096 -f ${keyPath} | Out-Null
    }       
  }

  if ($Public) {
    $keyPath = $keyPath + ".pub"
  }

  return $keyPath
}


function Get-ConfigKeyWords {
  return @("Match",
    "AddressFamily",
    "BatchMode",
    "BindAddress",
    "ChallengeResponseAuthentication",
    "CheckHostIP",
    "Cipher",
    "Ciphers",
    "ClearAllForwardings",
    "Compression",
    "CompressionLevel",
    "ConnectionAttempts",
    "ConnectTimeout",
    "ControlMaster",
    "ControlPath",
    "DynamicForward",
    "EscapeChar",
    "ExitOnForwardFailure",
    "ForwardAgent",
    "ForwardX11",
    "ForwardX11Trusted",
    "GatewayPorts",
    "GlobalKnownHostsFile",
    "GSSAPIAuthentication",
    "GSSAPIKeyExchange",
    "GSSAPIClientIdentity",
    "GSSAPIDelegateCredentials",
    "GSSAPIRenewalForcesRekey",
    "GSSAPITrustDns",
    "HashKnownHosts",
    "HostbasedAuthentication",
    "HostKeyAlgorithms",
    "HostKeyAlias",
    "HostName",
    "IdentitiesOnly",
    "IdentityFile",
    "KbdInteractiveAuthentication",
    "KbdInteractiveDevices",
    "LocalCommand",
    "LocalForward",
    "LogLevel",
    "MACs",
    "NoHostAuthenticationForLocalhost",
    "PreferredAuthentications",
    "Protocol",
    "ProxyCommand",
    "PubkeyAuthentication",
    "RemoteForward",
    "RhostsRSAAuthentication",
    "RSAAuthentication",
    "SendEnv",
    "ServerAliveCountMax",
    "ServerAliveInterval",
    "SmartcardDevice",
    "StrictHostKeyChecking",
    "TCPKeepAlive",
    "Tunnel",
    "TunnelDevice",
    "UsePrivilegedPort",
    "User",
    "UserKnownHostsFile",
    "VerifyHostKeyDNS",
    "VisualHostKey")
}

function Get-ConfigHostList {

  $hostList = @{}
  $configFile = Get-SshConfigFile

  if (-Not(Test-Path -Path $configFile)) {
    return $hostList
  }

  $contents = Get-Content $configFile -Raw

  # determine line break LF or CR/LF
  if ($contents -match "^[^\n]+\r\n") {
    $splitter = "\r\n"
  }
  else {
    $splitter = "\n"
  }

  # split by "Host" - when at start of file or has prededing line breaks / whitespaces
  $splitEntries = "(?i)(^|" + $splitter + "+\s+)host\s"
  $list = [regex]::Split($contents, $splitEntries)
  if ($list.Count -le 1) {
    throw "splitting file $configFilename failed or no content"
  }

  # READ lists of hosts

  foreach ($entry in $list) {
    # $output += $entry -replace $($splitter+"\s+"), $($joiner+" ")
    $attributes = [regex]::Split($entry, $splitter) | % { $_.Trim() }
    $HostName = $null
    $HostValues = @{}
    foreach ($attribute in $attributes) {
      if ($attribute -ne "") {
        if ($HostName) {
          # split key/value and normalize key name
          $kv = [regex]::Split($attribute, "\s+", 1)
          $keyName = $kv[0]
          $keyValue = $kv[1]
          foreach ($keyword in ($keywords | ? { $_ -eq $keyName })) {
            $keyName = $keyword                    
            break
          }
          $HostValues[$keyName] = $keyValue
        }
        else {
          # assume first entry to be the host
          $HostName = $attribute.ToLower()
        }
      }
    }
    if ($HostName) {
      if ($hostList.ContainsKey($HostName)) {
        throw "duplicate Host $HostName"
      }
      else {
        $hostList[$HostName] = $HostValues
      }
    }
  }

  return $hostList
}

function Add-ConfigHostToList {
  param (
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [hashtable]
    $hostList,
    [Parameter(Mandatory = $true)]
    [string]
    $HostName,
    [Parameter(Mandatory = $true)]
    [hashtable]
    $HostValues,
    [switch]
    $IgnoreExisting
  )

  if (!$IgnoreExisting) {
    if ($hostList.ContainsKey($HostName.ToLower())) {
      throw "HostName $HostName already exists"
    }
  }

  $keywords = Get-ConfigKeyWords
  $hostValuesCleaned = @{}
  foreach ($kv in $HostValues.GetEnumerator()) {
    $keyName = $null
    foreach ($keyword in ($keywords | ? { $_ -eq $kv.Key })) {
      $keyName = $keyword                    
      break
    }
    if ($keyName) {
      $hostValuesCleaned[$keyName] = $kv.Value.Trim()
    }
    else {
      throw "key $($kv.Key) not found in list of keywords" 
    }
  }
  if ($HostValuesCleaned) {
    $hostList[$HostName.ToLower()] = $HostValuesCleaned
  }

  return $hostList
}

function Set-ConfigContents {
  param (
    [Parameter(Mandatory = $true)]
    [string] $Contents
  )

  $configFilename = Get-SshConfigFile 
  if ($Contents) {
    $Contents | Set-Content $configFilename
  }
}

function Set-ConfigHostList {
  param (
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [hashtable] $hostList
  )

  $output = @()

  foreach ($hostEntry in $hostList.GetEnumerator()) {
    $hostOutput = "Host " + $hostEntry.Key + $joiner
    foreach ($kv in $hostEntry.Value.GetEnumerator()) {
      $hostOutput = $hostOutput + " " + $kv.key + " " + $kv.value + "`n"
    }
    $output += $hostOutput
  }

  if ($output) {
    $content = $($output -join "`n")
    $content | Set-Content (Get-SshConfigFile)
  }
}

function Set-SshAcl {
  param(
    [string] $Key
  )

  # TODO test

  $acl = Get-Acl -Path  $Key
  $acl.SetAccessRuleProtection($False, $False)
  $acl.RemoveAccessRuleAll($_)
  # icacls.exe $file /c /t /Grant ${env:UserName}:F # Set Ownership to Owner
  $acl.SetAccessRule((New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList (${env:UserName}) "FullControl" "Allow"))
  Set-Acl -Path $Key -AclObject $acl

  

}