PowerBash.psm1

$script:thisModulePath = $MyInvocation.MyCommand.ScriptBlock.Module.Path

function ConvertTo-LinuxPath
{
    param(
        [Parameter(Position=0, Mandatory=$true)]
        [string]$Path
    )

    if($Path.StartsWith("\\"))
    {
        Write-Error "UNC paths not currently supported"
        return
    }

    if($Path -match "^(\`"?)([A-Za-z])\:\\")
    {
        $quote = $Matches[1]
        $driveLetter = $Matches[2]

        if($driveLetter)
        {
            $Path = $Path -replace "^$quote$driveLetter\:\\","$quote/mnt/$($driveLetter.ToLower())/"
        }
    }

    $Path = $Path -replace "\\","/"
    $Path = $Path -replace " ","\ "
    $Path = $Path -replace "\(","\("
    $Path = $Path -replace "\)","\)"

    $Path
}
Set-Alias lxp ConvertTo-LinuxPath

function Get-AllBashApps
{
    $bashFSRoot = "$env:LOCALAPPDATA\lxss\rootfs"
    $bashPATH = "/usr/local/sbin","/usr/local/bin","/usr/sbin","/usr/bin","/sbin","/bin","/usr/lib/gcc/x86_64-linux-gnu/4.8","/usr/games"
    $allBashApps = @()
    foreach($path in $bashPATH)
    {
        $allBashApps += Get-ChildItem "$bashFSRoot$path" | ?{ $_.Extension -eq "" -and ("[") -notcontains $_.Name } |
                            Foreach-Object { [pscustomobject]@{ Name = $_.Name; Path = "$path/$($_.Name)" } }
    }

    $allBashApps
}

function Add-BashWrappers
{
    [CmdletBinding()]
    param(
        [Parameter(Position=0, Mandatory=$true)]
        $BashApps,
        [Parameter(Position=1, Mandatory=$false)]
        [string[]]$Overwrite=@()
    )

    $allPSCommandsHash = @{}
    foreach($c in ((Get-Command *).Name -replace "\.exe$","" | Where-Object { $Overwrite -notcontains $_ })){ $allPSCommandsHash[$c] = $true; }

    $BashApps | Where-Object { !$allPSCommandsHash[$_.Name] } -pv app |
        % { 
              New-Item -Path function: -Name $app.Name -Value {
                  begin
                  {
                      $allPipeArgs = @()

                      $hasPipeForward = $false
                      $invocationInfos = (Get-PSCallStack).InvocationInfo
                      foreach($invocation in ($invocationInfos | Select-Object -SkipLast 1))
                      {
                          if($hasPipeForward)
                          {
                              break
                          }

                          $hasPipeForward = ($invocation.PipelinePosition -ne $invocation.PipelineLength)
                          if(!$hasPipeForward)
                          {
                              $ast = [Management.Automation.Language.Parser]::ParseInput($invocation.Line, [ref]$null, [ref]$null)
                              $commandOffset = $invocation.OffsetInLine
                              $pipelineAst = $ast.EndBlock.Statements | Where-Object { $_.Extent.StartColumnNumber -le $commandOffset -and $_.Extent.EndColumnNumber -gt $commandOffset }
                              if($pipelineAst -isnot [System.Management.Automation.Language.PipelineAst])
                              {
                                  $hasPipeForward = $true
                              }
                              else
                              {
                                  $commandAst = $pipelineAst.PipelineElements | Where-Object { $_.Extent.StartColumnNumber -le $commandOffset -and $_.Extent.EndColumnNumber -gt $commandOffset }

                                  $hasPipeForward = (($commandAst -isnot [System.Management.Automation.Language.CommandAst]) -or ($commandAst.Redirections.Length -gt 0))
                              }
                          }
                      }
                  }

                  process
                  {
                      foreach($i in $input)
                      {
                          $hasPipeArgs = $true
                          $allPipeArgs += $i
                      }
                  }

                  end
                  {
                      try
                      {
                          $bashArgs = @()
                          foreach($a in $args)
                          {
                              if($a -ne "--%")
                              {
                                  $bashArgs += $a
                              }
                          }

                          $bashCommandStr = "$($app.Path) $bashArgs".Trim()
                          if($hasPipeArgs)
                          {
                              $tempStdIn = (New-TemporaryFile).FullName
                              [IO.File]::WriteAllLines($tempStdIn, $allPipeArgs)

                              $linuxStdIn = ConvertTo-LinuxPath $tempStdIn
                              $bashCommandStr += " <$linuxStdIn"
                          }

                          if($hasPipeForward)
                          {
                              $tempStdOut = (New-TemporaryFile).FullName
                              $tempStdErr = (New-TemporaryFile).FullName
                              $linuxStdOut = ConvertTo-LinuxPath $tempStdOut
                              $linuxStdErr = ConvertTo-LinuxPath $tempStdErr

                              $bashCommandStr += " >$linuxStdOut 2>$linuxStdErr"
                              $cmdCommandStr = "bash.exe -c `"$bashCommandStr`""
                              $p = start-process "cmd" -ArgumentList "/c",$cmdCommandStr -WindowStyle Hidden -PassThru
                              while(!$p.HasExited){ Start-Sleep -Milliseconds 1 }
                          }
                          else
                          {
                              bash.exe -c "$bashCommandStr"
                          }
                      }
                      finally
                      {
                          if($tempStdOut -and (Test-Path $tempStdOut))
                          {
                              Get-Content $tempStdOut
                              Remove-Item $tempStdOut
                          }

                          if($tempStdErr -and (Test-Path $tempStdErr))
                          {
                              Get-Content $tempStdErr | Write-Error
                              Remove-Item $tempStdErr
                          }

                          if($tempStdIn -and (Test-Path $tempStdIn))
                          {
                              Remove-Item $tempStdIn
                          }
                      }

                      if(($bashArgs.Length -ge 3 -and $app.Name -eq "sudo" -and $bashArgs[0] -eq "apt-get" -and $bashArgs[1] -eq "install") -or
                          ($bashArgs.Length -ge 2 -and $app.Name -eq "apt-get" -and $bashArgs[0] -eq "install"))
                      {
                          Import-Module $script:thisModulePath -Force -Global 3>$null
                      }
                  }
             }.GetNewClosure()
            
            Export-ModuleMember -Function $app.Name
            $allPSCommandsHash[$app.Name] = $true
        }
}

$script:allBashApps = Get-AllBashApps
. Add-BashWrappers $script:allBashApps $args

Set-PSReadlineKeyHandler -Key "Alt+l" `
                         -ScriptBlock {
    param($key, $arg)

    $ast = $null
    $tokens = $null
    $errors = $null
    $cursor = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$ast, [ref]$tokens, [ref]$errors, [ref]$cursor)

    $tokenToChange = $null
    foreach ($token in $tokens)
    {
        $extent = $token.Extent
        if ($extent.StartOffset -le $cursor -and $extent.EndOffset -ge $cursor)
        {
            $tokenToChange = $token

            if ($extent.EndOffset -eq $cursor -and $foreach.MoveNext())
            {
                $nextToken = $foreach.Current
                if ($nextToken.Extent.StartOffset -eq $cursor)
                {
                    $tokenToChange = $nextToken
                }
            }
            break
        }
    }

    if ($tokenToChange -ne $null)
    {
        $extent = $tokenToChange.Extent
        $tokenText = $extent.Text
        $replacement = ConvertTo-LinuxPath $tokenText

        [Microsoft.PowerShell.PSConsoleReadLine]::Replace(
            $extent.StartOffset,
            $tokenText.Length,
            $replacement)
    }
}

Export-ModuleMember -Function ConvertTo-LinuxPath
Export-ModuleMember -Alias lxp