summaryrefslogtreecommitdiffstats
path: root/test/support/windows-integration/collections/ansible_collections/ansible/windows/plugins/modules/win_stat.ps1
blob: 071eb11ceb121c096022d81a11ccd8134caf6eb3 (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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#!powershell

# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

#AnsibleRequires -CSharpUtil Ansible.Basic
#Requires -Module Ansible.ModuleUtils.FileUtil
#Requires -Module Ansible.ModuleUtils.LinkUtil

function ConvertTo-Timestamp($start_date, $end_date) {
    if ($start_date -and $end_date) {
        return (New-TimeSpan -Start $start_date -End $end_date).TotalSeconds
    }
}

function Get-FileChecksum($path, $algorithm) {
    switch ($algorithm) {
        'md5' { $sp = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider }
        'sha1' { $sp = New-Object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider }
        'sha256' { $sp = New-Object -TypeName System.Security.Cryptography.SHA256CryptoServiceProvider }
        'sha384' { $sp = New-Object -TypeName System.Security.Cryptography.SHA384CryptoServiceProvider }
        'sha512' { $sp = New-Object -TypeName System.Security.Cryptography.SHA512CryptoServiceProvider }
        default { Fail-Json -obj $result -message "Unsupported hash algorithm supplied '$algorithm'" }
    }

    $fp = [System.IO.File]::Open($path, [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
    try {
        $hash = [System.BitConverter]::ToString($sp.ComputeHash($fp)).Replace("-", "").ToLower()
    } finally {
        $fp.Dispose()
    }

    return $hash
}

function Get-FileInfo {
    param([String]$Path, [Switch]$Follow)

    $info = Get-AnsibleItem -Path $Path -ErrorAction SilentlyContinue
    $link_info = $null
    if ($null -ne $info) {
        try {
            $link_info = Get-Link -link_path $info.FullName
        } catch {
            $module.Warn("Failed to check/get link info for file: $($_.Exception.Message)")
        }

        # If follow=true we want to follow the link all the way back to root object
        if ($Follow -and $null -ne $link_info -and $link_info.Type -in @("SymbolicLink", "JunctionPoint")) {
            $info, $link_info = Get-FileInfo -Path $link_info.AbsolutePath -Follow
        }
    }

    return $info, $link_info
}

$spec = @{
    options = @{
        path = @{ type='path'; required=$true; aliases=@( 'dest', 'name' ) }
        get_checksum = @{ type='bool'; default=$true }
        checksum_algorithm = @{ type='str'; default='sha1'; choices=@( 'md5', 'sha1', 'sha256', 'sha384', 'sha512' ) }
        follow = @{ type='bool'; default=$false }
    }
    supports_check_mode = $true
}

$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)

$path = $module.Params.path
$get_checksum = $module.Params.get_checksum
$checksum_algorithm = $module.Params.checksum_algorithm
$follow = $module.Params.follow

$module.Result.stat = @{ exists=$false }

Load-LinkUtils
$info, $link_info = Get-FileInfo -Path $path -Follow:$follow
If ($null -ne $info) {
    $epoch_date = Get-Date -Date "01/01/1970"
    $attributes = @()
    foreach ($attribute in ($info.Attributes -split ',')) {
        $attributes += $attribute.Trim()
    }

    # default values that are always set, specific values are set below this
    # but are kept commented for easier readability
    $stat = @{
        exists = $true
        attributes = $info.Attributes.ToString()
        isarchive = ($attributes -contains "Archive")
        isdir = $false
        ishidden = ($attributes -contains "Hidden")
        isjunction = $false
        islnk = $false
        isreadonly = ($attributes -contains "ReadOnly")
        isreg = $false
        isshared = $false
        nlink = 1  # Number of links to the file (hard links), overriden below if islnk
        # lnk_target = islnk or isjunction Target of the symlink. Note that relative paths remain relative
        # lnk_source = islnk os isjunction Target of the symlink normalized for the remote filesystem
        hlnk_targets = @()
        creationtime = (ConvertTo-Timestamp -start_date $epoch_date -end_date $info.CreationTime)
        lastaccesstime = (ConvertTo-Timestamp -start_date $epoch_date -end_date $info.LastAccessTime)
        lastwritetime = (ConvertTo-Timestamp -start_date $epoch_date -end_date $info.LastWriteTime)
        # size = a file and directory - calculated below
        path = $info.FullName
        filename = $info.Name
        # extension = a file
        # owner = set outsite this dict in case it fails
        # sharename = a directory and isshared is True
        # checksum = a file and get_checksum: True
    }
    try {
        $stat.owner = $info.GetAccessControl().Owner
    } catch {
        # may not have rights, historical behaviour was to just set to $null
        # due to ErrorActionPreference being set to "Continue"
        $stat.owner = $null
    }

    # values that are set according to the type of file
    if ($info.Attributes.HasFlag([System.IO.FileAttributes]::Directory)) {
        $stat.isdir = $true
        $share_info = Get-CimInstance -ClassName Win32_Share -Filter "Path='$($stat.path -replace '\\', '\\')'"
        if ($null -ne $share_info) {
            $stat.isshared = $true
            $stat.sharename = $share_info.Name
        }

        try {
            $size = 0
            foreach ($file in $info.EnumerateFiles("*", [System.IO.SearchOption]::AllDirectories)) {
                $size += $file.Length
            }
            $stat.size = $size
        } catch {
            $stat.size = 0
        }
    } else {
        $stat.extension = $info.Extension
        $stat.isreg = $true
        $stat.size = $info.Length

        if ($get_checksum) {
            try {
                $stat.checksum = Get-FileChecksum -path $path -algorithm $checksum_algorithm
            } catch {
                $module.FailJson("Failed to get hash of file, set get_checksum to False to ignore this error: $($_.Exception.Message)", $_)
            }
        }
    }

    # Get symbolic link, junction point, hard link info
    if ($null -ne $link_info) {
        switch ($link_info.Type) {
            "SymbolicLink" {
                $stat.islnk = $true
                $stat.isreg = $false
                $stat.lnk_target = $link_info.TargetPath
                $stat.lnk_source = $link_info.AbsolutePath
                break
            }
            "JunctionPoint" {
                $stat.isjunction = $true
                $stat.isreg = $false
                $stat.lnk_target = $link_info.TargetPath
                $stat.lnk_source = $link_info.AbsolutePath
                break
            }
            "HardLink" {
                $stat.lnk_type = "hard"
                $stat.nlink = $link_info.HardTargets.Count

                # remove current path from the targets
                $hlnk_targets = $link_info.HardTargets | Where-Object { $_ -ne $stat.path }
                $stat.hlnk_targets = @($hlnk_targets)
                break
            }
        }
    }

    $module.Result.stat = $stat
}

$module.ExitJson()