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-FileInfonull36 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         follow = @{ type='bool'; default=$false }
63     }
64     supports_check_mode = $true
65 }
66 
67 $module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
68 
69 $path = $module.Params.path
70 $get_checksum = $module.Params.get_checksum
71 $checksum_algorithm = $module.Params.checksum_algorithm
72 $follow = $module.Params.follow
73 
74 $module.Result.stat = @{ exists=$false }
75 
76 Load-LinkUtils
77 $info, $link_info = Get-FileInfo -Path $path -Follow:$follow
78 If ($null -ne $info) {
79     $epoch_date = (Get-Date -Year 1970 -Month 1 -Day 1).Date
80     $attributes = @()
81     foreach ($attribute in ($info.Attributes -split ',')) {
82         $attributes += $attribute.Trim()
83     }
84 
85     # default values that are always set, specific values are set below this
86     # but are kept commented for easier readability
87     $stat = @{
88         exists = $true
89         attributes = $info.Attributes.ToString()
90         isarchive = ($attributes -contains "Archive")
91         isdir = $false
92         ishidden = ($attributes -contains "Hidden")
93         isjunction = $false
94         islnk = $false
95         isreadonly = ($attributes -contains "ReadOnly")
96         isreg = $false
97         isshared = $false
98         nlink = 1  # Number of links to the file (hard links), overriden below if islnk
99         # lnk_target = islnk or isjunction Target of the symlink. Note that relative paths remain relative
100         # lnk_source = islnk os isjunction Target of the symlink normalized for the remote filesystem
101         hlnk_targets = @()
102         creationtime = (ConvertTo-Timestamp -start_date $epoch_date -end_date $info.CreationTimeUtc)
103         lastaccesstime = (ConvertTo-Timestamp -start_date $epoch_date -end_date $info.LastAccessTimeUtc)
104         lastwritetime = (ConvertTo-Timestamp -start_date $epoch_date -end_date $info.LastWriteTimeUtc)
105         # size = a file and directory - calculated below
106         path = $info.FullName
107         filename = $info.Name
108         # extension = a file
109         # owner = set outsite this dict in case it fails
110         # sharename = a directory and isshared is True
111         # checksum = a file and get_checksum: True
112     }
113     try {
114         $stat.owner = $info.GetAccessControl().Owner
115     } catch {
116         # may not have rights, historical behaviour was to just set to $null
117         # due to ErrorActionPreference being set to "Continue"
118         $stat.owner = $null
119     }
120 
121     # values that are set according to the type of file
122     if ($info.Attributes.HasFlag([System.IO.FileAttributes]::Directory)) {
123         $stat.isdir = $true
124         $share_info = Get-CimInstance -ClassName Win32_Share -Filter "Path='$($stat.path -replace '\\', '\\')'"
125         if ($null -ne $share_info) {
126             $stat.isshared = $true
127             $stat.sharename = $share_info.Name
128         }
129 
130         try {
131             $size = 0
132             foreach ($file in $info.EnumerateFiles("*", [System.IO.SearchOption]::AllDirectories)) {
133                 $size += $file.Length
134             }
135             $stat.size = $size
136         } catch {
137             $stat.size = 0
138         }
139     } else {
140         $stat.extension = $info.Extension
141         $stat.isreg = $true
142         $stat.size = $info.Length
143 
144         if ($get_checksum) {
145             try {
146                 $stat.checksum = Get-FileChecksum -path $path -algorithm $checksum_algorithm
147             } catch {
148                 $module.FailJson("Failed to get hash of file, set get_checksum to False to ignore this error: $($_.Exception.Message)", $_)
149             }
150         }
151     }
152 
153     # Get symbolic link, junction point, hard link info
154     if ($null -ne $link_info) {
155         switch ($link_info.Type) {
156             "SymbolicLink" {
157                 $stat.islnk = $true
158                 $stat.isreg = $false
159                 $stat.lnk_target = $link_info.TargetPath
160                 $stat.lnk_source = $link_info.AbsolutePath
161                 break
162             }
163             "JunctionPoint" {
164                 $stat.isjunction = $true
165                 $stat.isreg = $false
166                 $stat.lnk_target = $link_info.TargetPath
167                 $stat.lnk_source = $link_info.AbsolutePath
168                 break
169             }
170             "HardLink" {
171                 $stat.lnk_type = "hard"
172                 $stat.nlink = $link_info.HardTargets.Count
173 
174                 # remove current path from the targets
175                 $hlnk_targets = $link_info.HardTargets | Where-Object { $_ -ne $stat.path }
176                 $stat.hlnk_targets = @($hlnk_targets)
177                 break
178             }
179         }
180     }
181 
182     $module.Result.stat = $stat
183 }
184 
185 $module.ExitJson()
186 
187