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