PSDependScripts/FileSystem.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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
<#
    .SYNOPSIS
        EXPERIMENTAL: Use Robocopy or Copy-Item for folder and file dependencies, respectively.

    .DESCRIPTION
        EXPERIMENTAL: Use Robocopy or Copy-Item for folder and file dependencies, respectively.

        Runs in the current session (i.e. as the current user)

        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 copy the source to. If the source is a file, we adjust this in Robocopy to append the source folder name.
            Source: The source folder or file to copy

    .PARAMETER Dependency
        Dependency to run

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

        Test: Return true or false on whether the dependency is in place
              IMPORTANT: If a folder exists, return $True, whether the contents are the same or not
                         If a file exists, we check the hash
        Install: Install the dependency
        Import: Import the dependency 'Target'. Override with ImportPath

    .PARAMETER ImportPath
        If specified with PSDependAction Import, we import this path, instead of the target (or target parent if target is a file)

    .PARAMETER Force
        If specified, and target is a folder, overwrite the target

    .PARAMETER Mirror
        If specified and the target is a folder, we effectively call robocopy /MIR (Can remove folders/files...)

    .EXAMPLE

        @{
            'notepad' = @{
                DependencyType = 'FileSystem'
                Source = 'C:\windows\notepad.exe'
                Target = 'C:\PSDependPesterTest'
            }
        }

        # Copy C:\Windows\Notepad.exe to C:\PSDependPesterTest\notepad.exe

    .EXAMPLE

        @{
            'psams' = @{
                DependencyType = 'FileSystem'
                Source = '\\FileServer\powershell\modules\psams'
                Target = 'C:\ProjectX'
            }
        }

        # Copy psams module to C:\ProjectX\psams

#>

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

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

    [string]$ImportPath
)

# Extract data from Dependency
    $DependencyName = $Dependency.DependencyName
    $Name = $Dependency.Name
    $Target = $Dependency.Target
    $Sources = @($Dependency.Source)

$TestOutput = @()
foreach($Source in @($Sources))
{
    if(-not (Test-Path $Source))
    {
        if(-not $PSDependAction -like 'Test')
        {
            Write-Error "Skipping $DependencyName, could not find source [$Sources] due to error:"
        }
        continue
    }
    $IsContainer = ( Get-Item $Source ).PSIsContainer
    
    # Resolve PSDrives.
    $Target = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Target)
    $Source = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Source)

    if($IsContainer)
    {
        $Folder = Split-Path $Source -Leaf
        $Target = Join-Path $Target $Folder
        if(-not $ImportPath) {$ImportPath = $Target}
        # We don't test equality for containers yet
        if(Test-Path $Target)
        {
            $TestOutput += $true
        }
        else
        {
            $TestOutput += $false
        }
        if($PSDependAction -like 'Install')
        {
            # TODO: Add non Windows equivalent...
            [string[]]$Arguments = "/XO"
            $Arguments += "/E"
            if($Dependency.Parameters.Mirror -eq $True -or $Mirror)
            {
                $Arguments += "/PURGE"
            }

            Write-Verbose "Invoking ROBOCOPY.exe $Source $Target $Arguments"
            ROBOCOPY.exe $Source $Target @Arguments
        }
    }
    else
    {
        $SourceFolderPath = Split-Path $Source -Parent
        $SourceFileName = Split-Path $Source -Leaf
        $TargetFile = Join-Path $Target $SourceFileName
        $SourceHash = ( Get-Hash $Source ).SHA256
        $TargetHash = $null
        if(Test-Path $Target -PathType Leaf)
        {
            $TargetHash = ( Get-Hash $Target -ErrorAction SilentlyContinue -WarningAction SilentlyContinue ).SHA256
            $TargetPath = $Target
        }
        elseif(Test-Path $TargetFile -PathType Leaf)
        {
            $TargetHash = ( Get-Hash $TargetFile -ErrorAction SilentlyContinue -WarningAction SilentlyContinue ).SHA256
            $TargetPath = $TargetFile
        }
        Write-Verbose "Source [$Source] hash [$SourceHash]`n`tTarget [$TargetPath] hash [$TargetHash]"

        if($TargetHash -ne $SourceHash)
        {
            Write-Verbose "Hashes do not match"
            if($PSDependAction -like 'Install')
            {
                Write-Verbose "Copying file [$Source] to [$Target]"
                Copy-Item -Path $Source -Destination $Target -Force
            }
            if($PSDependAction -like 'Test' -and $PSDependAction.count -eq 1)
            {
                $TestOutput += $false
            }
        }
        else
        {
            Write-Verbose "Matching hash: [$Source] = [$TargetFile]"
            if($PSDependAction -like 'Test' -and $PSDependAction.count -eq 1)
            {
                $TestOutput += $True
            }
        }
    }
}

if($PSDependAction -like 'Test' -and $PSDependAction.count -eq 1)
{
    if(@($TestOutput) -contains $false -or @($TestOutput) -notcontains $true)
    {
        return $false
    }
    else
    {
        return $True
    }
}

if($PSDependAction -like 'Import')
{
    if(-not $ImportPath)
    {
        if(Test-Path $Target -PathType Leaf)
        {
            $ImportPath = Split-Path $Target -Parent
        }
        elseif(Test-Path $Target -PathType Container)
        {
            $ImportPath = $Target
        }
        else
        {
            Write-Error "Could not import target [$Target], path not found"
            return
        }
    }
    Import-PSDependModule $ImportPath
}