PoShSQLLocalDB.psm1

function New-SQLLocalDBInstance
{
    <#
        .SYNOPSIS
            Creates a SQL Local DB Instance
        .DESCRIPTION
            Creates a SQL Local DB Instance
        .PARAMETER
        .EXAMPLE
        .NOTES
        .INPUTS
        .OUTPUTS
    #>

    [CmdletBinding()]
    param
    (
        [Parameter(Position=0, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String] $InstanceName
    )
    process
    {
        $Command = 'create "{0}"' -f $InstanceName
        Invoke-SQLLocalDBCommand -CommandParameters $Command
    }
}

function Remove-SQLLocalDBInstance
{
    [CmdletBinding()]
    param
    (
        [Parameter(Position=0, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String] $InstanceName,

        [Parameter(Position=1, Mandatory=$false)]
        [Switch] $Force
    )
    process
    {
        if($Force)
        {
            Stop-SQLLocalDBInstance -InstanceName $InstanceName -ShutdownNOWAIT
        }

        $Command = 'delete "{0}"' -f $InstanceName
        Invoke-SQLLocalDBCommand -CommandParameters $Command
    }
}

function Start-SQLLocalDBInstance
{
    [CmdletBinding()]
    param
    (
        [Parameter(Position=0, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String] $InstanceName
    )
    process
    {
        $Command = 'start "{0}"' -f $InstanceName
        Invoke-SQLLocalDBCommand -CommandParameters $Command
    }
}

function Stop-SQLLocalDBInstance
{
    [CmdletBinding(DefaultParameterSetName='BASIC')]
    param
    (
        [Parameter(Position=0, Mandatory=$true, ParameterSetName='BASIC')]
        [Parameter(Position=0, Mandatory=$true, ParameterSetName='NOWAIT')]
        [Parameter(Position=0, Mandatory=$true, ParameterSetName='KILL')]
        [ValidateNotNullOrEmpty()]
        [String] $InstanceName,

        # [Parameter(Position=1, Mandatory=$false, ParameterSetName='BASIC')]
        [Parameter(Position=1, Mandatory=$false, ParameterSetName='NOWAIT')]
        [Switch] $ShutdownNOWAIT,

        # [Parameter(Position=2, Mandatory=$false, ParameterSetName='BASIC')]
        [Parameter(Position=2, Mandatory=$false, ParameterSetName='KILL')]
        [Switch] $ExternalKill
    )
    process
    {
        $Command = 'stop "{0}"' -f $InstanceName

        if($ShutdownNOWAIT)
        {
            $Command += ' -i'
        }
        elseif($ExternalKill)
        {
            $Command += ' -k'
        }

        Invoke-SQLLocalDBCommand -CommandParameters $Command
    }
}

function Start-SQLLocalDBTraceAPI
{
    [CmdletBinding()]
    param
    (
    )
    process
    {
        $Command = 'trace on'
        Invoke-SQLLocalDBCommand -CommandParameters $Command
    }
}

function Stop-SQLLocalDBTraceAPI
{
    [CmdletBinding()]
    param
    (
    )
    process
    {
        $Command = 'trace off'
        Invoke-SQLLocalDBCommand -CommandParameters $Command
    }
}

function Get-SQLLocalDBInstance
{
    [CmdletBinding()]
    param
    (
        [Parameter(Position=0, Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [String[]] $InstanceName
    )
    process
    {

        if(-not $InstanceName)
        {
            [String[]] $InstanceName = @()
            $Command = 'info'

            $Results = Invoke-SQLLocalDBCommand -CommandParameters $Command
            $Lines = $Results -split "`n"
            $Lines |  ForEach-Object {
                if($_ -ne '')
                {
                    $InstanceName += $_
                }
            }
        }

        foreach($Instance in $InstanceName)
        {
            $Command = 'info "{0}"' -f $Instance
            $Results = Invoke-SQLLocalDBCommand -CommandParameters $Command
            $Lines = $Results -split "`n"
            $OutputObject = New-Object -TypeName PSObject;
            $OutputObject.PSObject.TypeNames.Insert(0,'PoShSQLLocalDB.Instance')
            $Lines |  ForEach-Object {
                if($_ -ne '')
                {
                    $Prop = ([regex]::Match($_,'^.*?(?=:)').Value -split ' ' -split '-' | ForEach-Object { $CamelArr = $_ -split ''; $CamelArr[1] = $CamelArr[1].ToUpper(); $CamelArr -join '' }) -join ''
                    $Value = [regex]::Match($_,'(?<=\w:).+$').Value -replace '^ +' -replace '(?<=\w)\s+$'
                    $OutputObject | Add-Member -MemberType NoteProperty -Name $Prop -Value $Value
                }
            }
            $OutputObject
        }
    }
}

function Get-SQLLocalDBVersions
{
    [CmdletBinding()]
    param
    (
    )
    process
    {
        $Command = 'versions'
        $Results = Invoke-SQLLocalDBCommand -CommandParameters $Command
        $Lines = $Results -split "`n"
        $Lines |  ForEach-Object {
            if($_ -ne '')
            {
                $OutputObject = New-Object -TypeName PSObject
                $OutputObject.PSObject.TypeNames.Insert(0,'PoShSQLLocalDB.Version')
                $OutputObject | Add-Member -MemberType NoteProperty -Name 'Version' -Value ($_ -replace '^ +' -replace '(?<=\w)\s+$')
                $OutputObject
            }
        }
    }
}

function Add-SQLLocalDBSharedInstance
{
    [CmdletBinding()]
    param
    (
        [Parameter(Position=0, Mandatory=$true, ParameterSetName='ALL')]
        [ValidateNotNullOrEmpty()]
        [String] $InstanceName,

        [Parameter(Position=1, Mandatory=$true, ParameterSetName='ALL')]
        [ValidateNotNullOrEmpty()]
        [String] $SharedInstanceName,

        [Parameter(Position=2, Mandatory=$false, ParameterSetName='ALL')]
        [Parameter(Position=2, Mandatory=$false, ParameterSetName='SID')]
        [ValidateNotNullOrEmpty()]
        [String] $UserSID,

        [Parameter(Position=2, Mandatory=$false, ParameterSetName='ALL')]
        [Parameter(Position=2, Mandatory=$false, ParameterSetName='USER')]
        [ValidateNotNullOrEmpty()]
        [String] $UserAccount
    )
    process
    {
        $Command = 'share'

        if($UserSID)
        {
            $Command += (' "{0}"' -f $UserSID)
        }
        elseif($UserAccount)
        {
            $Command += (' "{0}"' -f $UserAccount)
        }
        else
        {
            $Command += ' ""'
        }

        $Command += ' "{0}" "{1}"' -f $InstanceName, $SharedInstanceName
        Invoke-SQLLocalDBCommand -CommandParameters $Command -RunAsAdministrator
        Write-Warning -Message ('You must restart the instance: "{0}" before sharing will take effect.' -f $InstanceName)
    }
}

function Remove-SQLLocalDBSharedInstance
{
    [CmdletBinding()]
    param
    (
        [Parameter(Position=0, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String] $SharedInstanceName
    )
    process
    {
        $Command = 'unshare "{0}"' -f $SharedInstanceName
        Invoke-SQLLocalDBCommand -CommandParameters $Command -RunAsAdministrator
    }
}

<#
# Heavy Lifting
#>

function Invoke-SQLLocalDBCommand
{
    param
    (
        [Parameter(Position=0, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String] $CommandParameters,

        [Parameter(Position=0, Mandatory=$false)]
        [Switch] $RunAsAdministrator
    )
    begin
    {
        ##
        if($RunAsAdministrator)
        {
            $ErrorOutFile = '{0}\output\ErrorOut.log' -f $PSScriptRoot
            $StandardOutFile = '{0}\output\StandardOut.log' -f $PSScriptRoot
            $null = Remove-Item -Path $StandardOutFile -Force -ErrorAction SilentlyContinue
            $null = Remove-Item -Path $ErrorOutFile -Force -ErrorAction SilentlyContinue
        }

        if(-not (Get-Command -Name 'SqlLocalDB.exe' -ErrorAction SilentlyContinue))
        {
            Write-Verbose -Message 'SQLLocalDB.exe is not the Environment Path attempting to locate the install.'
            $SQLLocalDBPath = Find-SQLLocalDBBinary
            Write-Verbose -Message ('Using latest version found: "{0}"' -f $SQLLocalDBPath)
        }
        else
        {
            $SQLLocalDBPath = 'SQLLocalDB.exe'
        }
    }
    process
    {
        ## Build Process Start Information
        $ProcessInfo = New-Object -TypeName System.Diagnostics.ProcessStartInfo

        if($RunAsAdministrator)
        {
            $ProcessInfo.FileName = 'PowerShell.exe'
            $ProcessInfo.Verb = 'runas'
            $ProcessInfo.UseShellExecute = $true
            $ProcessInfo.WindowStyle = 'hidden'
            $ProcessInfo.CreateNoWindow = $true
            $CommandParameters = '"{0}" {1} 1> "{2}" 2> "{3}"' -f $SQLLocalDBPath, $CommandParameters, $StandardOutFile, $ErrorOutFile
            $ProcessInfo.Arguments = $CommandParameters
            Write-Verbose -Message ('Executing SQLLocalDB command as Admin: {0}' -f $CommandParameters);
        }
        else
        {
            $ProcessInfo.FileName = "$SQLLocalDBPath"
            $ProcessInfo.RedirectStandardError = $true
            $ProcessInfo.RedirectStandardOutput = $true
            $ProcessInfo.UseShellExecute = $false
            $ProcessInfo.Arguments = $CommandParameters
            Write-Verbose -Message ('Executing SQLLocalDB command: SQLLocalDB.exe {0}' -f $CommandParameters);
        }

        ## Build and Start Process
        $SQLLocalDBProcess = New-Object -TypeName System.Diagnostics.Process
        $SQLLocalDBProcess.StartInfo = $ProcessInfo
        $SQLLocalDBProcess.Start() | Out-Null

        if(-not $RunAsAdministrator)
        {
            ## Get Outputs
            $SQLLocalDBProcessStandardOut = $SQLLocalDBProcess.StandardOutput.ReadToEnd()
            $SQLLocalDBProcessStandardError = $SQLLocalDBProcess.StandardError.ReadToEnd()
        }

        ## Make Wait For Exit call after the ReadToEnd on Output Streams to prevent app deadlock
        ## some apps do not write to out asynchronously and making the wait for exit call before
        ## retrieving output can cause the app deadlock.
        $SQLLocalDBProcess.WaitForExit()

        if($RunAsAdministrator)
        {
            $SQLLocalDBProcessStandardOut = Get-Content -Path $StandardOutFile -ErrorAction SilentlyContinue
            $SQLLocalDBProcessStandardError = Get-Content -Path $ErrorOutFile -ErrorAction SilentlyContinue
        }

        ## Get Exit Code
        $SQLLocalDBProcessExitCode = $SQLLocalDBProcess.ExitCode

        if($SQLLocalDBProcessExitCode -eq 0 -and -not $SQLLocalDBProcessStandardError)
        {
            Write-Output $SQLLocalDBProcessStandardOut
        }
        else
        {
            if($SQLLocalDBProcessStandardOut)
            {
                Write-Output $SQLLocalDBProcessStandardOut

            }

            Write-Verbose ('exitcode: {0}.' -f $SQLLocalDBProcessExitCode)

            throw $SQLLocalDBProcessStandardError
        }
    }
}

## Consider dynamic param for version
## Consider friendly version name parameter list
function Find-SQLLocalDBBinary
{
    <#
        .SYNOPSIS
 
        .DESCRIPTION
 
        .PARAMETER
        .EXAMPLE
        .NOTES
        .INPUTS
        .OUTPUTS
    #>

    [CmdletBinding()]
    param
    (
        [Parameter(Position=0, Mandatory=$false)]
        [ValidateSet('120','130')]
        [String] $SQLVersion
    )
    begin
    {
        [String] $BaseSQLInstallRegistry = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\'
        [String] $ClientToolsSubPath = 'tools\ClientSetup'
        [String] $LocalDBEXE = 'SQLLocalDB.exe'
    }
    process
    {
        $PossibleLocations = Get-ChildItem $BaseSQLInstallRegistry | Where-Object { ($_.PSChildName -match '^\d+$') -and (Test-Path (Join-Path $_.PSPath $ClientToolsSubPath)) -and ($_.PSChildName -ilike "*$SQLVersion")}

        foreach($Install in $PossibleLocations)
        {
            $ToolsBinnPath = (Get-Item (Join-Path $Install.PSPath $ClientToolsSubPath)).GetValue('Path')
            if($ToolsBinnPath)
            {
                $BinPath = if(Test-Path (Join-Path $ToolsBinnPath $LocalDBEXE))
                {
                    Join-Path $ToolsBinnPath $LocalDBEXE
                }

                if($BinPath)
                {
                    $MessagePart = 'found'
                }
                else
                {
                    $MessagePart = 'not found'
                }

                Write-Verbose -Message ('SQLLocalDB.exe was {0} for version: "{1}". Bin Path: "{2}".' -f $MessagePart, $Install.PSChildName, $BinPath)
            }
        }

        if(-not $BinPath)
        {
            Throw 'Unable to locate SQLLocalDB.exe. It is not installed.'
        }
        else
        {
            $BinPath
        }
    }
}