PSDependScripts/FileDownload.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
<#
    .SYNOPSIS
        Download a file

    .DESCRIPTION
        Download a file

        Relevant Dependency metadata:
            DependencyName (Key): The key for this dependency is used as the URL. This can be overridden by 'Source'
            Name: Optional file name for the downloaded file. Defaults to parsing filename from the URL
            Target: The folder to download this file to. If a full path to a new file is used, this overrides any other file name.
            Source: Optional override for URL
            AddToPath: If specified, prepend the target's parent container to PATH

    .PARAMETER PSDependAction
        Test or Install the module. Defaults to Install

        Test: Return true or false on whether the dependency is in place
        Install: Install the dependency

    .EXAMPLE
        sqllite_dll = @{
            DependencyType = 'FileDownload'
            Source = 'https://github.com/RamblingCookieMonster/PSSQLite/blob/master/PSSQLite/x64/System.Data.SQLite.dll?raw=true'
            Target = 'C:\temp'
        }

        # Downloads System.Data.SQLite.dll to C:\temp

    .EXAMPLE
        'https://github.com/RamblingCookieMonster/PSSQLite/blob/master/PSSQLite/x64/System.Data.SQLite.dll?raw=true' = @{
            DependencyType = 'FileDownload'
            Target = 'C:\temp\sqlite.dll'
        }

        # Downloads System.Data.SQLite.dll to C:\temp\sqlite.dll
#>

[cmdletbinding()]
param(
    [PSTypeName('PSDepend.Dependency')]
    [psobject[]]
    $Dependency,

    [ValidateSet('Test', 'Install')]
    [string[]]$PSDependAction = @('Install')
)

function Parse-URLForFile {
[cmdletbinding()]
param($URL)
    # This will need work. Assume leaf is file. If CGI exists in leaf, assume it is after the file
    $FileName = $URL.split('/')[-1]
    if($FileName -match '\?')
    {
        $FileName.split('?')[0]
    }
    else
    {
        $FileName
    }
    Write-Verbose "Parsed file name [$FileName] from `$URL"
}

# Extract data from Dependency
    $DependencyName = $Dependency.DependencyName
    $Name = $Dependency.Name
    $Target = $Dependency.Target
    $Source = $Dependency.Source

    # Pick the URL
    if($Source)
    {
        $URL = $Source
    }
    else
    {
        $URL = $DependencyName
    }
    Write-Verbose "Using URL: $URL"

# Act on target path....
    $ToInstall = $False # Anti pattern
    $TargetParent = Split-Path $Target -Parent
    $PathToAdd = $Target
    if( (Test-Path $TargetParent) -and -not (Test-Path $Target))
    {
        # They gave us a full path, don't parse the file name, use this!
        $Path = $Target
        $ToInstall = $True
        Write-Verbose "Found parent [$TargetParent], not target [$Target], assuming this is target file path"
    }
    elseif(Test-Path $Target -PathType Leaf)
    {
        # File exists. We should download to temp spot, compare hashes, take action as appropriate.
        # For now, skip the file.
        Write-Verbose "Skipping existing file [$Target]"
        if($PSDependAction -contains 'Test')
        {
            return $True
        }
        $PathToAdd = Split-Path $Target -Parent
    }
    elseif(-not (Test-Path $Target))
    {
        # They gave us something that doesn't look like a new container for a new or existing file. Wat?
        Write-Error "Could not find target path [$Target]"
        if($PSDependAction -contains 'Test')
        {
            return $False
        }
    }
    else
    {
        Write-Verbose "[$Target] is a container, creating path to file"
        # We have a target container, now find the name
        If($Name)
        {
            # explicit name
            $FileName = $Name
        }
        else
        {
            $FileName = Parse-URLForFile -URL $URL
        }
        $Path = Join-Path $Target $FileName
        
        if(Test-Path $Path -PathType Leaf)
        {
            Write-Verbose "Skipping existing file [$Path]"
            if($PSDependAction -contains 'Test')
            {
                return $True
            }
        }
        else
        {
            $ToInstall = $True
        }
    }
    Write-Verbose "Using [$Path] as `$Target"

    #No dependency found, return false if we're testing alone...
    if( $PSDependAction -contains 'Test' -and $PSDependAction.count -eq 1)
    {
        return $False
    }
    Write-Verbose "Downloading [$URL] to [$Path]"

if($PSDependAction -contains 'Install' -and $ToInstall)
{
    # Future considerations:
        # Should we check for existing? And if we find it, still download file, and compare sha256 hash, replace if it does not match?
        # We should consider credentials at some point, but PSD1 does not lend itself to securely storing passwords
    Get-WebFile -URL $URL -Path $Path
}

if($Dependency.AddToPath)
{   
    Write-Verbose "Setting PATH to`n$($PathToAdd, $env:PATH -join ';' | Out-String)"
    Add-ToItemCollection -Reference Env:\Path -Item $PathToAdd
}