src/codeshare/assembly.ps1

# Copyright 2019, Adam Edwards
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

function IsDesktopEdition {
    $PSVersionTable.PSEdition -eq 'Desktop'
}

function FindAssembly($assemblyRoot, $assemblyName, $platformSpec) {
    write-verbose "Looking for matching assembly for '$assemblyName' under path '$assemblyRoot' with platform '$platformSpec'"
    # For OS compatibility, canonicalize path separators below by replacing '\' with '/'
    $matchingAssemblyPaths = get-childitem -r $assemblyRoot -Filter "$assemblyName.dll" | where {
        $assemblyDirectory = $_.fullname
        $components = $assemblyDirectory.replace("`\", "/") -split "/"

        if ( $components.length -ge 3 ) {
            ($components[$components.length - 2] -eq $platformSpec) -and ($components[$components.length - 3] -eq 'lib')
        }
    }

    if ( $matchingAssemblyPaths -and $matchingAssemblyPaths -is [object[]] -and $matchingAssemblyPaths.length -gt 1 ) {
        throw ("More than one assembly was found to match assembly '$assemblyName' -- update assembly directory to have only one of the following assemblies: {0}" -f (($matchingAssemblyPaths | foreach { "'$($_.fullname)'" }) -join ', '))
    }

    if ($matchingAssemblyPaths) {
        write-verbose "Found possible assembly match for '$assemblyName' in '$($matchingAssemblyPaths[0])'"
        $matchingAssemblyPaths[0].fullname.replace("`\", "/")
    }
}

function LoadAssemblyFromRoot($assemblyRoot, $assemblyName, $platformSpec) {
    $specs = if ( $platformSpec ) {
        , @($platformSpec)
    } elseif ( IsDesktopEdition ) {
        , @('net45')
    } else {
        @('netstandard1.3', 'netstandard1.1', 'netcoreapp1.0')
    }

    $assemblyPath = $null

    write-verbose ("Looking for assemblies with candidate platforms {0}" -f ( $specs -join ',' ) )

    for ( $specIndex = 0; $specIndex -lt $specs.length; $specIndex++ ) {
        $spec = $specs[$specIndex]
        $assemblyPath = FindAssembly $assemblyRoot $assemblyName $spec
        if ( $assemblyPath ) {
            break
        }

        write-verbose "Assembly '$assemblyName' not found"
    }

    if ( ! $assemblyPath ) {
        throw "Unable to find assembly '$assemblyName' under root directory '$assemblyRoot'. Please re-run the installation command for this application and retry."
    }

    write-verbose "Requested assembly '$assemblyName', loading assembly '$assemblyPath'"
    LoadAssembly $assemblyPath
}

function LoadAssembly($assemblyPath) {
    [System.Reflection.Assembly]::LoadFrom($assemblyPath)
}

function Import-Assembly {
    [cmdletbinding()]
    param(
        [parameter(mandatory=$true)]
        [string] $AssemblyName,
        [string] $AssemblyRelativePath = $null,
        [string] $AssemblyRoot = $null,
        [string] $TargetFrameworkMoniker = $null
    )
    $searchRoot = if ( $assemblyRoot ) {
        $assemblyRoot
    } else {
        $callerScriptFile = (get-pscallstack)[1].scriptname
        if ( ! $callerScriptFile ) {
            throw [ArgumentException]::new("Cannot load assembly '$AssemblyName' using the specified relative path '$AssemblyRelativePath' because the script file path of the caller cannot be determined; it may be a dynamic script block. Use the AssemblyRoot parameter to specify an absolute search path instead")
        }
        split-path -parent (get-pscallstack)[1].scriptname
    }
    write-verbose "Using assembly root '$searchRoot'..."

    $assemblyFile = split-path -leaf $assemblyName

    if ( $assemblyFile -ne $assemblyName ) {
        throw "Parameter 'AssemblyName' must be a single name -- the specified value '$AssemblyName' contains path separators and is not valid."
    }

    $searchRootDirectory = if ( $AssemblyRelativePath ) {
        join-path $searchRoot $AssemblyRelativePath
    } else {
        $searchRoot
    }

    $searchRootItem = get-item $searchRootDirectory -erroraction ignore

    if ( $searchRootItem -eq $null ) {
        throw "Unable to find assembly '$assemblyName' because given search directory '$searchRootDirectory' was not accessible"
    }

    $searchRootFullyQualified = $searchRootItem.fullname

    write-verbose "Using fully qualified assembly root '$searchRootFullyQualified' to find assembly '$assemblyFile'..."

    LoadAssemblyFromRoot $searchRootFullyQualified  $assemblyFile $TargetFrameworkMoniker
}