2014-09-26 01:01:01 +00:00
#!powershell
2018-07-17 21:29:05 +00:00
# Copyright: (c) 2015, Paul Durivage <paul.durivage@rackspace.com>
# Copyright: (c) 2015, Tal Auslander <tal@cloudshare.com>
2017-08-29 00:11:10 +00:00
# Copyright: (c) 2017, Dag Wieers <dag@wieers.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
2014-09-26 01:01:01 +00:00
2018-11-18 22:17:13 +00:00
#AnsibleRequires -CSharpUtil Ansible.Basic
# Requires -Module Ansible.ModuleUtils.AddType
$spec = @ {
options = @ {
url = @ { type = 'str' ; required = $true }
dest = @ { type = 'path' ; required = $true }
timeout = @ { type = 'int' ; default = 10 }
headers = @ { type = 'dict' ; default = @ { } }
validate_certs = @ { type = 'bool' ; default = $true }
url_username = @ { type = 'str' ; aliases = @ ( 'username' ) }
url_password = @ { type = 'str' ; aliases = @ ( 'password' ) ; no_log = $true }
force_basic_auth = @ { type = 'bool' ; default = $false }
use_proxy = @ { type = 'bool' ; default = $true }
proxy_url = @ { type = 'str' }
proxy_username = @ { type = 'str' }
proxy_password = @ { type = 'str' ; no_log = $true }
force = @ { type = 'bool' ; default = $true }
}
supports_check_mode = $true
}
2017-08-29 00:11:10 +00:00
2018-11-18 22:17:13 +00:00
$module = [ Ansible.Basic.AnsibleModule ] :: Create ( $args , $spec )
$url = $module . Params . url
$dest = $module . Params . dest
$timeout = $module . Params . timeout
$headers = $module . Params . headers
$validate_certs = $module . Params . validate_certs
$url_username = $module . Params . url_username
$url_password = $module . Params . url_password
$force_basic_auth = $module . Params . force_basic_auth
$use_proxy = $module . Params . use_proxy
$proxy_url = $module . Params . proxy_url
$proxy_username = $module . Params . proxy_username
$proxy_password = $module . Params . proxy_password
$force = $module . Params . force
Add-CSharpType -AnsibleModule $module -References @ '
2017-08-29 22:52:39 +00:00
using System . Net ;
public class ExtendedWebClient : WebClient {
public int Timeout ;
public ExtendedWebClient ( ) {
Timeout = 600000 ; / / Default timeout value
}
protected override WebRequest GetWebRequest ( System . Uri address ) {
WebRequest request = base . GetWebRequest ( address ) ;
request . Timeout = Timeout ;
return request ;
}
}
2018-11-18 22:17:13 +00:00
' @
2017-08-29 22:52:39 +00:00
2018-11-18 22:17:13 +00:00
Function CheckModified-File($module , $url , $dest , $headers , $credentials , $timeout , $use_proxy , $proxy ) {
2017-08-29 00:11:10 +00:00
$fileLastMod = ( [ System.IO.FileInfo ] $dest ) . LastWriteTimeUtc
$webLastMod = $null
2018-05-03 22:39:37 +00:00
$webRequest = [ System.Net.WebRequest ] :: Create ( $url )
2018-08-31 20:20:56 +00:00
2017-08-29 00:11:10 +00:00
foreach ( $header in $headers . GetEnumerator ( ) ) {
$webRequest . Headers . Add ( $header . Name , $header . Value )
}
if ( $timeout ) {
$webRequest . Timeout = $timeout * 1000
}
if ( -not $use_proxy ) {
# Ignore the system proxy settings
$webRequest . Proxy = $null
} elseif ( $proxy ) {
$webRequest . Proxy = $proxy
}
if ( $credentials ) {
2018-05-03 22:39:37 +00:00
if ( $force_basic_auth ) {
2018-05-24 04:21:49 +00:00
$webRequest . Headers . Add ( " Authorization " , " Basic $credentials " )
2018-05-03 22:39:37 +00:00
} else {
2018-05-24 04:21:49 +00:00
$webRequest . Credentials = $credentials
2018-05-03 22:39:37 +00:00
}
2017-08-29 00:11:10 +00:00
}
2018-05-03 22:39:37 +00:00
if ( $webRequest -is [ System.Net.FtpWebRequest ] ) {
$webRequest . Method = [ System . Net . WebRequestMethods + Ftp ] :: GetDateTimestamp
} else {
$webRequest . Method = [ System . Net . WebRequestMethods + Http ] :: Head
}
2018-08-31 20:20:56 +00:00
2018-11-18 22:17:13 +00:00
# FIXME: Split both try-statements and single-out catched exceptions with more specific error messages
2017-08-29 00:11:10 +00:00
Try {
2018-05-03 22:39:37 +00:00
$webResponse = $webRequest . GetResponse ( )
$webLastMod = $webResponse . LastModified
2017-08-29 00:11:10 +00:00
} Catch [ System.Net.WebException ] {
2018-11-18 22:17:13 +00:00
$module . Result . status_code = [ int ] $_ . Exception . Response . StatusCode
$module . FailJson ( " Error requesting ' $url '. $( $_ . Exception . Message ) " , $_ )
2017-08-29 00:11:10 +00:00
} Catch {
2018-11-18 22:17:13 +00:00
$module . FailJson ( " Error when requesting 'Last-Modified' date from ' $url '. $( $_ . Exception . Message ) " , $_ )
2017-08-29 00:11:10 +00:00
}
2018-11-18 22:17:13 +00:00
$module . Result . status_code = [ int ] $webResponse . StatusCode
$module . Result . msg = [ string ] $webResponse . StatusDescription
2017-08-29 00:11:10 +00:00
$webResponse . Close ( )
2018-04-24 01:30:03 +00:00
if ( $webLastMod -and ( ( Get-Date -Date $webLastMod ) . ToUniversalTime ( ) -lt $fileLastMod ) ) {
2017-08-29 00:11:10 +00:00
return $false
} else {
return $true
}
}
2018-11-18 22:17:13 +00:00
Function Download-File($module , $url , $dest , $headers , $credentials , $timeout , $use_proxy , $proxy ) {
2017-08-29 00:11:10 +00:00
2018-08-31 20:20:56 +00:00
$module_start = Get-Date
2017-08-29 00:11:10 +00:00
# Check $dest parent folder exists before attempting download, which avoids unhelpful generic error message.
$dest_parent = Split-Path -LiteralPath $dest
if ( -not ( Test-Path -LiteralPath $dest_parent -PathType Container ) ) {
2018-11-18 22:17:13 +00:00
$module . FailJson ( " The path ' $dest_parent ' does not exist for destination ' $dest ', or is not visible to the current user. Ensure download destination folder exists (perhaps using win_file state=directory) before win_get_url runs. " )
2017-08-29 00:11:10 +00:00
}
# TODO: Replace this with WebRequest
2017-08-29 22:52:39 +00:00
$extWebClient = New-Object ExtendedWebClient
2017-08-29 00:11:10 +00:00
foreach ( $header in $headers . GetEnumerator ( ) ) {
2017-08-29 22:52:39 +00:00
$extWebClient . Headers . Add ( $header . Name , $header . Value )
2017-08-29 00:11:10 +00:00
}
2017-08-29 22:52:39 +00:00
if ( $timeout ) {
$extWebClient . Timeout = $timeout * 1000
}
2017-08-29 00:11:10 +00:00
if ( -not $use_proxy ) {
# Ignore the system proxy settings
2017-08-29 22:52:39 +00:00
$extWebClient . Proxy = $null
2017-08-29 00:11:10 +00:00
} elseif ( $proxy ) {
2017-08-29 22:52:39 +00:00
$extWebClient . Proxy = $proxy
2017-08-29 00:11:10 +00:00
}
if ( $credentials ) {
2017-12-18 20:38:44 +00:00
if ( $force_basic_auth ) {
$extWebClient . Headers . Add ( " Authorization " , " Basic $credentials " )
} else {
$extWebClient . Credentials = $credentials
}
2017-08-29 00:11:10 +00:00
}
2018-11-18 22:17:13 +00:00
if ( -not $module . CheckMode ) {
# FIXME: Single-out catched exceptions with more specific error messages
2017-08-29 22:52:39 +00:00
Try {
$extWebClient . DownloadFile ( $url , $dest )
} Catch [ System.Net.WebException ] {
2018-11-18 22:17:13 +00:00
$module . Result . status_code = [ int ] $_ . Exception . Response . StatusCode
$module . Result . elapsed = ( ( Get-Date ) - $module_start ) . TotalSeconds
$module . FailJson ( " Error downloading ' $url ' to ' $dest ': $( $_ . Exception . Message ) " , $_ )
2017-08-29 22:52:39 +00:00
} Catch {
2018-11-18 22:17:13 +00:00
$module . Result . elapsed = ( ( Get-Date ) - $module_start ) . TotalSeconds
$module . FailJson ( " Unknown error downloading ' $url ' to ' $dest ': $( $_ . Exception . Message ) " , $_ )
2017-08-29 00:11:10 +00:00
}
}
2017-08-29 22:52:39 +00:00
2018-11-18 22:17:13 +00:00
$module . Result . status_code = 200
$module . Result . changed = $true
$module . Result . msg = 'OK'
$module . Result . dest = $dest
$module . Result . elapsed = ( ( Get-Date ) - $module_start ) . TotalSeconds
2018-08-31 20:20:56 +00:00
2017-08-29 00:11:10 +00:00
}
2018-11-18 22:17:13 +00:00
$module . Result . dest = $dest
$module . Result . elapsed = 0
$module . Result . url = $url
2014-09-26 01:01:01 +00:00
2017-08-29 00:11:10 +00:00
if ( -not $use_proxy -and ( $proxy_url -or $proxy_username -or $proxy_password ) ) {
2018-11-18 22:17:13 +00:00
$module . Warn ( " Not using a proxy on request, however a 'proxy_url', 'proxy_username' or 'proxy_password' was defined. " )
2017-08-29 00:11:10 +00:00
}
$proxy = $null
if ( $proxy_url ) {
$proxy = New-Object System . Net . WebProxy ( $proxy_url , $true )
if ( $proxy_username -and $proxy_password ) {
$proxy_credential = New-Object System . Net . NetworkCredential ( $proxy_username , $proxy_password )
$proxy . Credentials = $proxy_credential
}
}
$credentials = $null
2018-05-03 22:39:37 +00:00
if ( $url_username ) {
2017-12-18 20:38:44 +00:00
if ( $force_basic_auth ) {
$credentials = [ convert ] :: ToBase64String ( [ System.Text.Encoding ] :: ASCII . GetBytes ( $url_username + " : " + $url_password ) )
} else {
2018-09-11 04:23:48 +00:00
$credentials = New-Object System . Net . NetworkCredential ( $url_username , $url_password )
2017-12-18 20:38:44 +00:00
}
2017-08-29 00:11:10 +00:00
}
2017-07-05 23:56:43 +00:00
if ( -not $validate_certs ) {
2017-08-29 00:11:10 +00:00
[ System.Net.ServicePointManager ] :: ServerCertificateValidationCallback = { $true }
2015-06-25 20:52:23 +00:00
}
2017-08-29 00:11:10 +00:00
# Use last part of url for dest file name if a directory is supplied for $dest
if ( Test-Path -LiteralPath $dest -PathType Container ) {
$uri = [ System.Uri ] $url
$basename = Split-Path -Path $uri . LocalPath -Leaf
if ( $uri . LocalPath -and $uri . LocalPath -ne '/' -and $basename ) {
$url_basename = Split-Path -Path $uri . LocalPath -Leaf
$dest = Join-Path -Path $dest -ChildPath $url_basename
} else {
$dest = Join-Path -Path $dest -ChildPath $uri . Host
2015-06-08 11:45:20 +00:00
}
2018-11-18 22:17:13 +00:00
# Ensure we have a string instead of a PS object to avoid serialization issues
$dest = $dest . ToString ( )
2017-08-29 00:11:10 +00:00
} elseif ( ( [ System.IO.Path ] :: GetFileName ( $dest ) ) -eq '' ) {
# We have a trailing path separator
2018-11-18 22:17:13 +00:00
$module . FailJson ( " The destination path ' $dest ' does not exist, or is not visible to the current user. Ensure download destination folder exists (perhaps using win_file state=directory) before win_get_url runs. " )
2016-03-18 08:41:54 +00:00
}
2018-11-18 22:17:13 +00:00
$module . Result . dest = $dest
2016-03-18 08:41:54 +00:00
2017-08-11 01:00:34 +00:00
# Enable TLS1.1/TLS1.2 if they're available but disabled (eg. .NET 4.5)
2018-08-31 20:20:56 +00:00
$security_protocols = [ Net.ServicePointManager ] :: SecurityProtocol -bor [ Net.SecurityProtocolType ] :: SystemDefault
2017-08-11 01:00:34 +00:00
if ( [ Net.SecurityProtocolType ] . GetMember ( " Tls11 " ) . Count -gt 0 ) {
2018-08-31 20:20:56 +00:00
$security_protocols = $security_protocols -bor [ Net.SecurityProtocolType ] :: Tls11
2017-08-11 01:00:34 +00:00
}
if ( [ Net.SecurityProtocolType ] . GetMember ( " Tls12 " ) . Count -gt 0 ) {
2018-08-31 20:20:56 +00:00
$security_protocols = $security_protocols -bor [ Net.SecurityProtocolType ] :: Tls12
2017-08-11 01:00:34 +00:00
}
2018-08-31 20:20:56 +00:00
[ Net.ServicePointManager ] :: SecurityProtocol = $security_protocols
2016-03-18 08:41:54 +00:00
2017-08-29 00:11:10 +00:00
if ( $force -or -not ( Test-Path -LiteralPath $dest ) ) {
2016-03-18 08:41:54 +00:00
2018-11-18 22:17:13 +00:00
Download-File -module $module -url $url -dest $dest -credentials $credentials `
-headers $headers -timeout $timeout -use_proxy $use_proxy -proxy $proxy
2015-06-25 20:52:23 +00:00
2017-08-29 00:11:10 +00:00
} else {
2015-06-25 20:52:23 +00:00
2018-11-18 22:17:13 +00:00
$is_modified = CheckModified-File -module $module -url $url -dest $dest -credentials $credentials `
2017-08-29 00:11:10 +00:00
-headers $headers -timeout $timeout -use_proxy $use_proxy -proxy $proxy
2016-03-18 08:41:54 +00:00
2017-08-29 00:11:10 +00:00
if ( $is_modified ) {
2016-03-18 08:41:54 +00:00
2018-11-18 22:17:13 +00:00
Download-File -module $module -url $url -dest $dest -credentials $credentials `
-headers $headers -timeout $timeout -use_proxy $use_proxy -proxy $proxy
2016-03-18 08:41:54 +00:00
2017-08-29 00:11:10 +00:00
}
2014-09-26 01:01:01 +00:00
}
2018-11-18 22:17:13 +00:00
$module . ExitJson ( )