1 #!powershell
2 
3 # Copyright: (c) 2017, Ansible Project
4 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5 
6 #AnsibleRequires -CSharpUtil Ansible.Basic
7 #Requires -Module Ansible.ModuleUtils.FileUtil
8 #Requires -Module Ansible.ModuleUtils.LinkUtil
9 
ConvertTo-Timestamp($start_date, $end_date)10 function ConvertTo-Timestamp($start_date, $end_date) {
11     if ($start_date -and $end_date) {
12         return (New-TimeSpan -Start $start_date -End $end_date).TotalSeconds
13     }
14 }
15 
Get-FileChecksum($path, $algorithm)16 function Get-FileChecksum($path, $algorithm) {
17     switch ($algorithm) {
18         'md5' { $sp = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider }
19         'sha1' { $sp = New-Object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider }
20         'sha256' { $sp = New-Object -TypeName System.Security.Cryptography.SHA256CryptoServiceProvider }
21         'sha384' { $sp = New-Object -TypeName System.Security.Cryptography.SHA384CryptoServiceProvider }
22         'sha512' { $sp = New-Object -TypeName System.Security.Cryptography.SHA512CryptoServiceProvider }
23         default { Fail-Json -obj $result -message "Unsupported hash algorithm supplied '$algorithm'" }
24     }
25 
26     $fp = [System.IO.File]::Open($path, [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
27     try {
28         $hash = [System.BitConverter]::ToString($sp.ComputeHash($fp)).Replace("-", "").ToLower()
29     } finally {
30         $fp.Dispose()
31     }
32 
33     return $hash
34 }
35 
Get-FileInfo()36 function Get-FileInfo {
37     param([String]$Path, [Switch]$Follow)
38 
39     $info = Get-AnsibleItem -Path $Path -ErrorAction SilentlyContinue
40     $link_info = $null
41     if ($null -ne $info) {
42         try {
43             $link_info = Get-Link -link_path $info.FullName
44         } catch {
45             $module.Warn("Failed to check/get link info for file: $($_.Exception.Message)")
46         }
47 
48         # If follow=true we want to follow the link all the way back to root object
49         if ($Follow -and $null -ne $link_info -and $link_info.Type -in @("SymbolicLink", "JunctionPoint")) {
50             $info, $link_info = Get-FileInfo -Path $link_info.AbsolutePath -Follow
51         }
52     }
53 
54     return $info, $link_info
55 }
56 
57 $spec = @{
58     options = @{
59         path = @{ type='path'; required=$true; aliases=@( 'dest', 'name' ) }
60         get_checksum = @{ type='bool'; default=$true }
61         checksum_algorithm = @{ type='str'; default='sha1'; choices=@( 'md5', 'sha1', 'sha256', 'sha384', 'sha512' ) }
62         get_md5 = @{ type='bool'; default=$false; removed_in_version='2.9' }
63         follow = @{ type='bool'; default=$false }
64     }
65     supports_check_mode = $true
66 }
67 
68 $module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
69 
70 $path = $module.Params.path
71 $get_md5 = $module.Params.get_md5
72 $get_checksum = $module.Params.get_checksum
73 $checksum_algorithm = $module.Params.checksum_algorithm
74 $follow = $module.Params.follow
75 
76 $module.Result.stat = @{ exists=$false }
77 
78 Load-LinkUtils
79 $info, $link_info = Get-FileInfo -Path $path -Follow:$follow
80 If ($null -ne $info) {
81     $epoch_date = Get-Date -Date "01/01/1970"
82     $attributes = @()
83     foreach ($attribute in ($info.Attributes -split ',')) {
84         $attributes += $attribute.Trim()
85     }
86 
87     # default values that are always set, specific values are set below this
88     # but are kept commented for easier readability
89     $stat = @{
90         exists = $true
91         attributes = $info.Attributes.ToString()
92         isarchive = ($attributes -contains "Archive")
93         isdir = $false
94         ishidden = ($attributes -contains "Hidden")
95         isjunction = $false
96         islnk = $false
97         isreadonly = ($attributes -contains "ReadOnly")
98         isreg = $false
99         isshared = $false
100         nlink = 1  # Number of links to the file (hard links), overriden below if islnk
101         # lnk_target = islnk or isjunction Target of the symlink. Note that relative paths remain relative
102         # lnk_source = islnk os isjunction Target of the symlink normalized for the remote filesystem
103         hlnk_targets = @()
104         creationtime = (ConvertTo-Timestamp -start_date $epoch_date -end_date $info.CreationTime)
105         lastaccesstime = (ConvertTo-Timestamp -start_date $epoch_date -end_date $info.LastAccessTime)
106         lastwritetime = (ConvertTo-Timestamp -start_date $epoch_date -end_date $info.LastWriteTime)
107         # size = a file and directory - calculated below
108         path = $info.FullName
109         filename = $info.Name
110         # extension = a file
111         # owner = set outsite this dict in case it fails
112         # sharename = a directory and isshared is True
113         # checksum = a file and get_checksum: True
114         # md5 = a file and get_md5: True
115     }
116     try {
117         $stat.owner = $info.GetAccessControl().Owner
118     } catch {
119         # may not have rights, historical behaviour was to just set to $null
120         # due to ErrorActionPreference being set to "Continue"
121         $stat.owner = $null
122     }
123 
124     # values that are set according to the type of file
125     if ($info.Attributes.HasFlag([System.IO.FileAttributes]::Directory)) {
126         $stat.isdir = $true
127         $share_info = Get-CimInstance -ClassName Win32_Share -Filter "Path='$($stat.path -replace '\\', '\\')'"
128         if ($null -ne $share_info) {
129             $stat.isshared = $true
130             $stat.sharename = $share_info.Name
131         }
132 
133         try {
134             $size = 0
135             foreach ($file in $info.EnumerateFiles("*", [System.IO.SearchOption]::AllDirectories)) {
136                 $size += $file.Length
137             }
138             $stat.size = $size
139         } catch {
140             $stat.size = 0
141         }
142     } else {
143         $stat.extension = $info.Extension
144         $stat.isreg = $true
145         $stat.size = $info.Length
146 
147         if ($get_md5) {
148             try {
149                 $stat.md5 = Get-FileChecksum -path $path -algorithm "md5"
150             } catch {
151                 $module.FailJson("Failed to get MD5 hash of file, remove get_md5 to ignore this error: $($_.Exception.Message)", $_)
152             }
153         }
154         if ($get_checksum) {
155             try {
156                 $stat.checksum = Get-FileChecksum -path $path -algorithm $checksum_algorithm
157             } catch {
158                 $module.FailJson("Failed to get hash of file, set get_checksum to False to ignore this error: $($_.Exception.Message)", $_)
159             }
160         }
161     }
162 
163     # Get symbolic link, junction point, hard link info
164     if ($null -ne $link_info) {
165         switch ($link_info.Type) {
166             "SymbolicLink" {
167                 $stat.islnk = $true
168                 $stat.isreg = $false
169                 $stat.lnk_target = $link_info.TargetPath
170                 $stat.lnk_source = $link_info.AbsolutePath
171                 break
172             }
173             "JunctionPoint" {
174                 $stat.isjunction = $true
175                 $stat.isreg = $false
176                 $stat.lnk_target = $link_info.TargetPath
177                 $stat.lnk_source = $link_info.AbsolutePath
178                 break
179             }
180             "HardLink" {
181                 $stat.lnk_type = "hard"
182                 $stat.nlink = $link_info.HardTargets.Count
183 
184                 # remove current path from the targets
185                 $hlnk_targets = $link_info.HardTargets | Where-Object { $_ -ne $stat.path }
186                 $stat.hlnk_targets = @($hlnk_targets)
187                 break
188             }
189         }
190     }
191 
192     $module.Result.stat = $stat
193 }
194 
195 $module.ExitJson()
196 
197