summaryrefslogtreecommitdiff
path: root/lib/ansible/modules/windows/win_unzip.ps1
blob: 4bcf9a9406e80038ae51355cffc67326093c9310 (plain)
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
#!powershell

# Copyright: (c) 2015, Phil Schwartz <schwartzmx@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

#Requires -Module Ansible.ModuleUtils.Legacy

# TODO: This module is not idempotent (it will always unzip and report change)

$ErrorActionPreference = "Stop"

$pcx_extensions = @('.bz2', '.gz', '.msu', '.tar', '.zip')

$params = Parse-Args $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false

$src = Get-AnsibleParam -obj $params -name "src" -type "path" -failifempty $true
$dest = Get-AnsibleParam -obj $params -name "dest" -type "path" -failifempty $true
$creates = Get-AnsibleParam -obj $params -name "creates" -type "path"
$recurse = Get-AnsibleParam -obj $params -name "recurse" -type "bool" -default $false
$delete_archive = Get-AnsibleParam -obj $params -name "delete_archive" -type "bool" -default $false -aliases 'rm'

# Fixes a fail error message (when the task actually succeeds) for a
# "Convert-ToJson: The converted JSON string is in bad format"
# This happens when JSON is parsing a string that ends with a "\",
# which is possible when specifying a directory to download to.
# This catches that possible error, before assigning the JSON $result
$result = @{
    changed = $false
    dest = $dest -replace '\$',''
    removed = $false
    src = $src -replace '\$',''
}

Function Extract-Zip($src, $dest) {
    $archive = [System.IO.Compression.ZipFile]::Open($src, [System.IO.Compression.ZipArchiveMode]::Read, [System.Text.Encoding]::UTF8)
    foreach ($entry in $archive.Entries) {
        $archive_name = $entry.FullName

        $entry_target_path = [System.IO.Path]::Combine($dest, $archive_name)
        $entry_dir = [System.IO.Path]::GetDirectoryName($entry_target_path)

        if (-not (Test-Path -Path $entry_dir)) {
            New-Item -Path $entry_dir -ItemType Directory -WhatIf:$check_mode | Out-Null
            $result.changed = $true
        }

        if ((-not ($entry_target_path.EndsWith("\") -or $entry_target_path.EndsWith("/"))) -and (-not $check_mode)) {
            [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $entry_target_path, $true)
        }
        $result.changed = $true
    }
    $archive.Dispose()
}

Function Extract-ZipLegacy($src, $dest) {
    # [System.IO.Compression.ZipFile] was only added in .net 4.5, this is used
    # when .net is older than that.
    $shell = New-Object -ComObject Shell.Application
    $zip = $shell.NameSpace([IO.Path]::GetFullPath($src))
    $dest_path = $shell.NameSpace([IO.Path]::GetFullPath($dest))

    $shell = New-Object -ComObject Shell.Application

    if (-not $check_mode) {
        # https://msdn.microsoft.com/en-us/library/windows/desktop/bb787866.aspx
        # From Folder.CopyHere documentation, 1044 means:
        #  - 1024: do not display a user interface if an error occurs
        #  -   16: respond with "yes to all" for any dialog box that is displayed
        #  -    4: do not display a progress dialog box
        $dest_path.CopyHere($zip.Items(), 1044)
    }
    $result.changed = $true
}

If ($creates -and (Test-Path -LiteralPath $creates)) {
    $result.skipped = $true
    $result.msg = "The file or directory '$creates' already exists."
    Exit-Json -obj $result
}

If (-Not (Test-Path -LiteralPath $src)) {
    Fail-Json -obj $result -message "File '$src' does not exist."
}

$ext = [System.IO.Path]::GetExtension($src)

If (-Not (Test-Path -LiteralPath $dest -PathType Container)){
    Try{
        New-Item -ItemType "directory" -path $dest -WhatIf:$check_mode | out-null
    } Catch {
        Fail-Json -obj $result -message "Error creating '$dest' directory! Msg: $($_.Exception.Message)"
    }
}

If ($ext -eq ".zip" -And $recurse -eq $false) {
    # TODO: PS v5 supports zip extraction, use that if available
    $use_legacy = $false
    try {
        # determines if .net 4.5 is available, if this fails we need to fall
        # back to the legacy COM Shell.Application to extract the zip
        Add-Type -AssemblyName System.IO.Compression.FileSystem | Out-Null
        Add-Type -AssemblyName System.IO.Compression | Out-Null
    } catch {
        $use_legacy = $true
    }

    if ($use_legacy) {
        try {
            Extract-ZipLegacy -src $src -dest $dest
        } catch {
            Fail-Json -obj $result -message "Error unzipping '$src' to '$dest'!. Method: COM Shell.Application, Exception: $($_.Exception.Message)"
        }
    } else {
        try {
            Extract-Zip -src $src -dest $dest
        } catch {
            Fail-Json -obj $result -message "Error unzipping '$src' to '$dest'!. Method: System.IO.Compression.ZipFile, Exception: $($_.Exception.Message)"
        }
    }
} Else {
    # Check if PSCX is installed
    $list = Get-Module -ListAvailable

    If (-Not ($list -match "PSCX")) {
        Fail-Json -obj $result -message "PowerShellCommunityExtensions PowerShell Module (PSCX) is required for non-'.zip' compressed archive types."
    } Else {
        $result.pscx_status = "present"
    }

    Try {
        Import-Module PSCX
    }
    Catch {
        Fail-Json $result "Error importing module PSCX"
    }

    Try {
        Expand-Archive -Path $src -OutputPath $dest -Force -WhatIf:$check_mode
    } Catch {
        Fail-Json -obj $result -message "Error expanding '$src' to '$dest'! Msg: $($_.Exception.Message)"
    }

    If ($recurse) {
        Get-ChildItem $dest -recurse | Where {$pcx_extensions -contains $_.extension} | % {
            Try {
                Expand-Archive $_.FullName -OutputPath $dest -Force -WhatIf:$check_mode
            } Catch {
                Fail-Json -obj $result -message "Error recursively expanding '$src' to '$dest'! Msg: $($_.Exception.Message)"
            }
            If ($delete_archive) {
                Remove-Item $_.FullName -Force -WhatIf:$check_mode
                $result.removed = $true
            }
        }
    }

    $result.changed = $true
}

If ($delete_archive){
    try {
        Remove-Item $src -Recurse -Force -WhatIf:$check_mode
    } catch {
        Fail-Json -obj $result -message "failed to delete archive at '$src': $($_.Exception.Message)"
    }
    $result.removed = $true
}
Exit-Json $result