summaryrefslogtreecommitdiffstats
path: root/lib/ansible/module_utils/powershell
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ansible/module_utils/powershell')
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1398
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.ArgvParser.psm178
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Backup.psm134
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm169
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1107
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.FileUtil.psm166
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1390
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.LinkUtil.psm1464
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.PrivilegeUtil.psm183
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.SID.psm199
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1530
-rw-r--r--lib/ansible/module_utils/powershell/__init__.py0
12 files changed, 2318 insertions, 0 deletions
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1
new file mode 100644
index 0000000..6dc2917
--- /dev/null
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1
@@ -0,0 +1,398 @@
+# Copyright (c) 2018 Ansible Project
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+Function Add-CSharpType {
+ <#
+ .SYNOPSIS
+ Compiles one or more C# scripts similar to Add-Type. This exposes
+ more configuration options that are useable within Ansible and it
+ also allows multiple C# sources to be compiled together.
+
+ .PARAMETER References
+ [String[]] A collection of C# scripts to compile together.
+
+ .PARAMETER IgnoreWarnings
+ [Switch] Whether to compile code that contains compiler warnings, by
+ default warnings will cause a compiler error.
+
+ .PARAMETER PassThru
+ [Switch] Whether to return the loaded Assembly
+
+ .PARAMETER AnsibleModule
+ [Ansible.Basic.AnsibleModule] used to derive the TempPath and Debug values.
+ TempPath is set to the Tmpdir property of the class
+ IncludeDebugInfo is set when the Ansible verbosity is >= 3
+
+ .PARAMETER TempPath
+ [String] The temporary directory in which the dynamic assembly is
+ compiled to. This file is deleted once compilation is complete.
+ Cannot be used when AnsibleModule is set. This is a no-op when
+ running on PSCore.
+
+ .PARAMETER IncludeDebugInfo
+ [Switch] Whether to include debug information in the compiled
+ assembly. Cannot be used when AnsibleModule is set. This is a no-op
+ when running on PSCore.
+
+ .PARAMETER CompileSymbols
+ [String[]] A list of symbols to be defined during compile time. These are
+ added to the existing symbols, 'CORECLR', 'WINDOWS', 'UNIX' that are set
+ conditionalls in this cmdlet.
+
+ .NOTES
+ The following features were added to control the compiling options from the
+ code itself.
+
+ * Predefined compiler SYMBOLS
+
+ * CORECLR - Added when running on PowerShell Core.
+ * WINDOWS - Added when running on Windows.
+ * UNIX - Added when running on non-Windows.
+ * X86 - Added when running on a 32-bit process (Ansible 2.10+)
+ * AMD64 - Added when running on a 64-bit process (Ansible 2.10+)
+
+ * Ignore compiler warnings inline with the following comment inline
+
+ //NoWarn -Name <rule code> [-CLR Core|Framework]
+
+ * Specify custom assembly references inline
+
+ //AssemblyReference -Name Dll.Location.dll [-CLR Core|Framework]
+
+ # Added in Ansible 2.10
+ //AssemblyReference -Type System.Type.Name [-CLR Core|Framework]
+
+ * Create automatic type accelerators to simplify long namespace names (Ansible 2.9+)
+
+ //TypeAccelerator -Name <AcceleratorName> -TypeName <Name of compiled type>
+ #>
+ param(
+ [Parameter(Mandatory = $true)][AllowEmptyCollection()][String[]]$References,
+ [Switch]$IgnoreWarnings,
+ [Switch]$PassThru,
+ [Parameter(Mandatory = $true, ParameterSetName = "Module")][Object]$AnsibleModule,
+ [Parameter(ParameterSetName = "Manual")][String]$TempPath = $env:TMP,
+ [Parameter(ParameterSetName = "Manual")][Switch]$IncludeDebugInfo,
+ [String[]]$CompileSymbols = @()
+ )
+ if ($null -eq $References -or $References.Length -eq 0) {
+ return
+ }
+
+ # define special symbols CORECLR, WINDOWS, UNIX if required
+ # the Is* variables are defined on PSCore, if absent we assume an
+ # older version of PowerShell under .NET Framework and Windows
+ $defined_symbols = [System.Collections.ArrayList]$CompileSymbols
+
+ if ([System.IntPtr]::Size -eq 4) {
+ $defined_symbols.Add('X86') > $null
+ }
+ else {
+ $defined_symbols.Add('AMD64') > $null
+ }
+
+ $is_coreclr = Get-Variable -Name IsCoreCLR -ErrorAction SilentlyContinue
+ if ($null -ne $is_coreclr) {
+ if ($is_coreclr.Value) {
+ $defined_symbols.Add("CORECLR") > $null
+ }
+ }
+ $is_windows = Get-Variable -Name IsWindows -ErrorAction SilentlyContinue
+ if ($null -ne $is_windows) {
+ if ($is_windows.Value) {
+ $defined_symbols.Add("WINDOWS") > $null
+ }
+ else {
+ $defined_symbols.Add("UNIX") > $null
+ }
+ }
+ else {
+ $defined_symbols.Add("WINDOWS") > $null
+ }
+
+ # Store any TypeAccelerators shortcuts the util wants us to set
+ $type_accelerators = [System.Collections.Generic.List`1[Hashtable]]@()
+
+ # pattern used to find referenced assemblies in the code
+ $assembly_pattern = [Regex]"//\s*AssemblyReference\s+-(?<Parameter>(Name)|(Type))\s+(?<Name>[\w.]*)(\s+-CLR\s+(?<CLR>Core|Framework))?"
+ $no_warn_pattern = [Regex]"//\s*NoWarn\s+-Name\s+(?<Name>[\w\d]*)(\s+-CLR\s+(?<CLR>Core|Framework))?"
+ $type_pattern = [Regex]"//\s*TypeAccelerator\s+-Name\s+(?<Name>[\w.]*)\s+-TypeName\s+(?<TypeName>[\w.]*)"
+
+ # PSCore vs PSDesktop use different methods to compile the code,
+ # PSCore uses Roslyn and can compile the code purely in memory
+ # without touching the disk while PSDesktop uses CodeDom and csc.exe
+ # to compile the code. We branch out here and run each
+ # distribution's method to add our C# code.
+ if ($is_coreclr) {
+ # compile the code using Roslyn on PSCore
+
+ # Include the default assemblies using the logic in Add-Type
+ # https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs
+ $assemblies = [System.Collections.Generic.HashSet`1[Microsoft.CodeAnalysis.MetadataReference]]@(
+ [Microsoft.CodeAnalysis.CompilationReference]::CreateFromFile(([System.Reflection.Assembly]::GetAssembly([PSObject])).Location)
+ )
+ $netcore_app_ref_folder = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName([PSObject].Assembly.Location), "ref")
+ $lib_assembly_location = [System.IO.Path]::GetDirectoryName([object].Assembly.Location)
+ foreach ($file in [System.IO.Directory]::EnumerateFiles($netcore_app_ref_folder, "*.dll", [System.IO.SearchOption]::TopDirectoryOnly)) {
+ $assemblies.Add([Microsoft.CodeAnalysis.MetadataReference]::CreateFromFile($file)) > $null
+ }
+
+ # loop through the references, parse as a SyntaxTree and get
+ # referenced assemblies
+ $ignore_warnings = New-Object -TypeName 'System.Collections.Generic.Dictionary`2[[String], [Microsoft.CodeAnalysis.ReportDiagnostic]]'
+ $parse_options = ([Microsoft.CodeAnalysis.CSharp.CSharpParseOptions]::Default).WithPreprocessorSymbols($defined_symbols)
+ $syntax_trees = [System.Collections.Generic.List`1[Microsoft.CodeAnalysis.SyntaxTree]]@()
+ foreach ($reference in $References) {
+ # scan through code and add any assemblies that match
+ # //AssemblyReference -Name ... [-CLR Core]
+ # //NoWarn -Name ... [-CLR Core]
+ # //TypeAccelerator -Name ... -TypeName ...
+ $assembly_matches = $assembly_pattern.Matches($reference)
+ foreach ($match in $assembly_matches) {
+ $clr = $match.Groups["CLR"].Value
+ if ($clr -and $clr -ne "Core") {
+ continue
+ }
+
+ $parameter_type = $match.Groups["Parameter"].Value
+ $assembly_path = $match.Groups["Name"].Value
+ if ($parameter_type -eq "Type") {
+ $assembly_path = ([Type]$assembly_path).Assembly.Location
+ }
+ else {
+ if (-not ([System.IO.Path]::IsPathRooted($assembly_path))) {
+ $assembly_path = Join-Path -Path $lib_assembly_location -ChildPath $assembly_path
+ }
+ }
+ $assemblies.Add([Microsoft.CodeAnalysis.MetadataReference]::CreateFromFile($assembly_path)) > $null
+ }
+ $warn_matches = $no_warn_pattern.Matches($reference)
+ foreach ($match in $warn_matches) {
+ $clr = $match.Groups["CLR"].Value
+ if ($clr -and $clr -ne "Core") {
+ continue
+ }
+ $ignore_warnings.Add($match.Groups["Name"], [Microsoft.CodeAnalysis.ReportDiagnostic]::Suppress)
+ }
+ $syntax_trees.Add([Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree]::ParseText($reference, $parse_options)) > $null
+
+ $type_matches = $type_pattern.Matches($reference)
+ foreach ($match in $type_matches) {
+ $type_accelerators.Add(@{Name = $match.Groups["Name"].Value; TypeName = $match.Groups["TypeName"].Value })
+ }
+ }
+
+ # Release seems to contain the correct line numbers compared to
+ # debug,may need to keep a closer eye on this in the future
+ $compiler_options = (New-Object -TypeName Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions -ArgumentList @(
+ [Microsoft.CodeAnalysis.OutputKind]::DynamicallyLinkedLibrary
+ )).WithOptimizationLevel([Microsoft.CodeAnalysis.OptimizationLevel]::Release)
+
+ # set warnings to error out if IgnoreWarnings is not set
+ if (-not $IgnoreWarnings.IsPresent) {
+ $compiler_options = $compiler_options.WithGeneralDiagnosticOption([Microsoft.CodeAnalysis.ReportDiagnostic]::Error)
+ $compiler_options = $compiler_options.WithSpecificDiagnosticOptions($ignore_warnings)
+ }
+
+ # create compilation object
+ $compilation = [Microsoft.CodeAnalysis.CSharp.CSharpCompilation]::Create(
+ [System.Guid]::NewGuid().ToString(),
+ $syntax_trees,
+ $assemblies,
+ $compiler_options
+ )
+
+ # Load the compiled code and pdb info, we do this so we can
+ # include line number in a stracktrace
+ $code_ms = New-Object -TypeName System.IO.MemoryStream
+ $pdb_ms = New-Object -TypeName System.IO.MemoryStream
+ try {
+ $emit_result = $compilation.Emit($code_ms, $pdb_ms)
+ if (-not $emit_result.Success) {
+ $errors = [System.Collections.ArrayList]@()
+
+ foreach ($e in $emit_result.Diagnostics) {
+ # builds the error msg, based on logic in Add-Type
+ # https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs#L1239
+ if ($null -eq $e.Location.SourceTree) {
+ $errors.Add($e.ToString()) > $null
+ continue
+ }
+
+ $cancel_token = New-Object -TypeName System.Threading.CancellationToken -ArgumentList $false
+ $text_lines = $e.Location.SourceTree.GetText($cancel_token).Lines
+ $line_span = $e.Location.GetLineSpan()
+
+ $diagnostic_message = $e.ToString()
+ $error_line_string = $text_lines[$line_span.StartLinePosition.Line].ToString()
+ $error_position = $line_span.StartLinePosition.Character
+
+ $sb = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($diagnostic_message.Length + $error_line_string.Length * 2 + 4)
+ $sb.AppendLine($diagnostic_message)
+ $sb.AppendLine($error_line_string)
+
+ for ($i = 0; $i -lt $error_line_string.Length; $i++) {
+ if ([System.Char]::IsWhiteSpace($error_line_string[$i])) {
+ continue
+ }
+ $sb.Append($error_line_string, 0, $i)
+ $sb.Append(' ', [Math]::Max(0, $error_position - $i))
+ $sb.Append("^")
+ break
+ }
+
+ $errors.Add($sb.ToString()) > $null
+ }
+
+ throw [InvalidOperationException]"Failed to compile C# code:`r`n$($errors -join "`r`n")"
+ }
+
+ $code_ms.Seek(0, [System.IO.SeekOrigin]::Begin) > $null
+ $pdb_ms.Seek(0, [System.IO.SeekOrigin]::Begin) > $null
+ $compiled_assembly = [System.Runtime.Loader.AssemblyLoadContext]::Default.LoadFromStream($code_ms, $pdb_ms)
+ }
+ finally {
+ $code_ms.Close()
+ $pdb_ms.Close()
+ }
+ }
+ else {
+ # compile the code using CodeDom on PSDesktop
+
+ # configure compile options based on input
+ if ($PSCmdlet.ParameterSetName -eq "Module") {
+ $temp_path = $AnsibleModule.Tmpdir
+ $include_debug = $AnsibleModule.Verbosity -ge 3
+ }
+ else {
+ $temp_path = $TempPath
+ $include_debug = $IncludeDebugInfo.IsPresent
+ }
+ $compiler_options = [System.Collections.ArrayList]@("/optimize")
+ if ($defined_symbols.Count -gt 0) {
+ $compiler_options.Add("/define:" + ([String]::Join(";", $defined_symbols.ToArray()))) > $null
+ }
+
+ $compile_parameters = New-Object -TypeName System.CodeDom.Compiler.CompilerParameters
+ $compile_parameters.GenerateExecutable = $false
+ $compile_parameters.GenerateInMemory = $true
+ $compile_parameters.TreatWarningsAsErrors = (-not $IgnoreWarnings.IsPresent)
+ $compile_parameters.IncludeDebugInformation = $include_debug
+ $compile_parameters.TempFiles = (New-Object -TypeName System.CodeDom.Compiler.TempFileCollection -ArgumentList $temp_path, $false)
+
+ # Add-Type automatically references System.dll, System.Core.dll,
+ # and System.Management.Automation.dll which we replicate here
+ $assemblies = [System.Collections.Generic.HashSet`1[String]]@(
+ "System.dll",
+ "System.Core.dll",
+ ([System.Reflection.Assembly]::GetAssembly([PSObject])).Location
+ )
+
+ # create a code snippet for each reference and check if we need
+ # to reference any extra assemblies
+ $ignore_warnings = [System.Collections.ArrayList]@()
+ $compile_units = [System.Collections.Generic.List`1[System.CodeDom.CodeSnippetCompileUnit]]@()
+ foreach ($reference in $References) {
+ # scan through code and add any assemblies that match
+ # //AssemblyReference -Name ... [-CLR Framework]
+ # //NoWarn -Name ... [-CLR Framework]
+ # //TypeAccelerator -Name ... -TypeName ...
+ $assembly_matches = $assembly_pattern.Matches($reference)
+ foreach ($match in $assembly_matches) {
+ $clr = $match.Groups["CLR"].Value
+ if ($clr -and $clr -ne "Framework") {
+ continue
+ }
+
+ $parameter_type = $match.Groups["Parameter"].Value
+ $assembly_path = $match.Groups["Name"].Value
+ if ($parameter_type -eq "Type") {
+ $assembly_path = ([Type]$assembly_path).Assembly.Location
+ }
+ $assemblies.Add($assembly_path) > $null
+ }
+ $warn_matches = $no_warn_pattern.Matches($reference)
+ foreach ($match in $warn_matches) {
+ $clr = $match.Groups["CLR"].Value
+ if ($clr -and $clr -ne "Framework") {
+ continue
+ }
+ $warning_id = $match.Groups["Name"].Value
+ # /nowarn should only contain the numeric part
+ if ($warning_id.StartsWith("CS")) {
+ $warning_id = $warning_id.Substring(2)
+ }
+ $ignore_warnings.Add($warning_id) > $null
+ }
+ $compile_units.Add((New-Object -TypeName System.CodeDom.CodeSnippetCompileUnit -ArgumentList $reference)) > $null
+
+ $type_matches = $type_pattern.Matches($reference)
+ foreach ($match in $type_matches) {
+ $type_accelerators.Add(@{Name = $match.Groups["Name"].Value; TypeName = $match.Groups["TypeName"].Value })
+ }
+ }
+ if ($ignore_warnings.Count -gt 0) {
+ $compiler_options.Add("/nowarn:" + ([String]::Join(",", $ignore_warnings.ToArray()))) > $null
+ }
+ $compile_parameters.ReferencedAssemblies.AddRange($assemblies)
+ $compile_parameters.CompilerOptions = [String]::Join(" ", $compiler_options.ToArray())
+
+ # compile the code together and check for errors
+ $provider = New-Object -TypeName Microsoft.CSharp.CSharpCodeProvider
+
+ # This calls csc.exe which can take compiler options from environment variables. Currently these env vars
+ # are known to have problems so they are unset:
+ # LIB - additional library paths will fail the compilation if they are invalid
+ $originalEnv = @{}
+ try {
+ 'LIB' | ForEach-Object -Process {
+ $value = Get-Item -LiteralPath "Env:\$_" -ErrorAction SilentlyContinue
+ if ($value) {
+ $originalEnv[$_] = $value
+ Remove-Item -LiteralPath "Env:\$_"
+ }
+ }
+
+ $compile = $provider.CompileAssemblyFromDom($compile_parameters, $compile_units)
+ }
+ finally {
+ foreach ($kvp in $originalEnv.GetEnumerator()) {
+ [System.Environment]::SetEnvironmentVariable($kvp.Key, $kvp.Value, "Process")
+ }
+ }
+
+ if ($compile.Errors.HasErrors) {
+ $msg = "Failed to compile C# code: "
+ foreach ($e in $compile.Errors) {
+ $msg += "`r`n" + $e.ToString()
+ }
+ throw [InvalidOperationException]$msg
+ }
+ $compiled_assembly = $compile.CompiledAssembly
+ }
+
+ $type_accelerator = [PSObject].Assembly.GetType("System.Management.Automation.TypeAccelerators")
+ foreach ($accelerator in $type_accelerators) {
+ $type_name = $accelerator.TypeName
+ $found = $false
+
+ foreach ($assembly_type in $compiled_assembly.GetTypes()) {
+ if ($assembly_type.Name -eq $type_name) {
+ $type_accelerator::Add($accelerator.Name, $assembly_type)
+ $found = $true
+ break
+ }
+ }
+ if (-not $found) {
+ throw "Failed to find compiled class '$type_name' for custom TypeAccelerator."
+ }
+ }
+
+ # return the compiled assembly if PassThru is set.
+ if ($PassThru) {
+ return $compiled_assembly
+ }
+}
+
+Export-ModuleMember -Function Add-CSharpType
+
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.ArgvParser.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.ArgvParser.psm1
new file mode 100644
index 0000000..53d6870
--- /dev/null
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.ArgvParser.psm1
@@ -0,0 +1,78 @@
+# Copyright (c) 2017 Ansible Project
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+# The rules used in these functions are derived from the below
+# https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments
+# https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
+
+Function Escape-Argument($argument, $force_quote = $false) {
+ # this converts a single argument to an escaped version, use Join-Arguments
+ # instead of this function as this only escapes a single string.
+
+ # check if argument contains a space, \n, \t, \v or "
+ if ($force_quote -eq $false -and $argument.Length -gt 0 -and $argument -notmatch "[ \n\t\v`"]") {
+ # argument does not need escaping (and we don't want to force it),
+ # return as is
+ return $argument
+ }
+ else {
+ # we need to quote the arg so start with "
+ $new_argument = '"'
+
+ for ($i = 0; $i -lt $argument.Length; $i++) {
+ $num_backslashes = 0
+
+ # get the number of \ from current char until end or not a \
+ while ($i -ne ($argument.Length - 1) -and $argument[$i] -eq "\") {
+ $num_backslashes++
+ $i++
+ }
+
+ $current_char = $argument[$i]
+ if ($i -eq ($argument.Length - 1) -and $current_char -eq "\") {
+ # We are at the end of the string so we need to add the same \
+ # * 2 as the end char would be a "
+ $new_argument += ("\" * ($num_backslashes + 1) * 2)
+ }
+ elseif ($current_char -eq '"') {
+ # we have a inline ", we need to add the existing \ but * by 2
+ # plus another 1
+ $new_argument += ("\" * (($num_backslashes * 2) + 1))
+ $new_argument += $current_char
+ }
+ else {
+ # normal character so no need to escape the \ we have counted
+ $new_argument += ("\" * $num_backslashes)
+ $new_argument += $current_char
+ }
+ }
+
+ # we need to close the special arg with a "
+ $new_argument += '"'
+ return $new_argument
+ }
+}
+
+Function Argv-ToString($arguments, $force_quote = $false) {
+ # Takes in a list of un escaped arguments and convert it to a single string
+ # that can be used when starting a new process. It will escape the
+ # characters as necessary in the list.
+ # While there is a CommandLineToArgvW function there is a no
+ # ArgvToCommandLineW that we can call to convert a list to an escaped
+ # string.
+ # You can also pass in force_quote so that each argument is quoted even
+ # when not necessary, by default only arguments with certain characters are
+ # quoted.
+ # TODO: add in another switch which will escape the args for cmd.exe
+
+ $escaped_arguments = @()
+ foreach ($argument in $arguments) {
+ $escaped_argument = Escape-Argument -argument $argument -force_quote $force_quote
+ $escaped_arguments += $escaped_argument
+ }
+
+ return ($escaped_arguments -join ' ')
+}
+
+# this line must stay at the bottom to ensure all defined module parts are exported
+Export-ModuleMember -Alias * -Function * -Cmdlet *
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Backup.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Backup.psm1
new file mode 100644
index 0000000..ca4f5ba
--- /dev/null
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Backup.psm1
@@ -0,0 +1,34 @@
+# Copyright (c): 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+Function Backup-File {
+ <#
+ .SYNOPSIS
+ Helper function to make a backup of a file.
+ .EXAMPLE
+ Backup-File -path $path -WhatIf:$check_mode
+#>
+ [CmdletBinding(SupportsShouldProcess = $true)]
+
+ Param (
+ [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
+ [string] $path
+ )
+
+ Process {
+ $backup_path = $null
+ if (Test-Path -LiteralPath $path -PathType Leaf) {
+ $backup_path = "$path.$pid." + [DateTime]::Now.ToString("yyyyMMdd-HHmmss") + ".bak";
+ Try {
+ Copy-Item -LiteralPath $path -Destination $backup_path
+ }
+ Catch {
+ throw "Failed to create backup file '$backup_path' from '$path'. ($($_.Exception.Message))"
+ }
+ }
+ return $backup_path
+ }
+}
+
+# This line must stay at the bottom to ensure all defined module parts are exported
+Export-ModuleMember -Function Backup-File
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1
new file mode 100644
index 0000000..9b86f84
--- /dev/null
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1
@@ -0,0 +1,69 @@
+# Copyright (c) 2017 Ansible Project
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+# used by Convert-DictToSnakeCase to convert a string in camelCase
+# format to snake_case
+Function Convert-StringToSnakeCase($string) {
+ # cope with pluralized abbreaviations such as TargetGroupARNs
+ if ($string -cmatch "[A-Z]{3,}s") {
+ $replacement_string = $string -creplace $matches[0], "_$($matches[0].ToLower())"
+
+ # handle when there was nothing before the plural pattern
+ if ($replacement_string.StartsWith("_") -and -not $string.StartsWith("_")) {
+ $replacement_string = $replacement_string.Substring(1)
+ }
+ $string = $replacement_string
+ }
+ $string = $string -creplace "(.)([A-Z][a-z]+)", '$1_$2'
+ $string = $string -creplace "([a-z0-9])([A-Z])", '$1_$2'
+ $string = $string.ToLower()
+
+ return $string
+}
+
+# used by Convert-DictToSnakeCase to covert list entries from camelCase
+# to snake_case
+Function Convert-ListToSnakeCase($list) {
+ $snake_list = [System.Collections.ArrayList]@()
+ foreach ($value in $list) {
+ if ($value -is [Hashtable]) {
+ $new_value = Convert-DictToSnakeCase -dict $value
+ }
+ elseif ($value -is [Array] -or $value -is [System.Collections.ArrayList]) {
+ $new_value = Convert-ListToSnakeCase -list $value
+ }
+ else {
+ $new_value = $value
+ }
+ [void]$snake_list.Add($new_value)
+ }
+
+ return , $snake_list
+}
+
+# converts a dict/hashtable keys from camelCase to snake_case
+# this is to keep the return values consistent with the Ansible
+# way of working.
+Function Convert-DictToSnakeCase($dict) {
+ $snake_dict = @{}
+ foreach ($dict_entry in $dict.GetEnumerator()) {
+ $key = $dict_entry.Key
+ $snake_key = Convert-StringToSnakeCase -string $key
+
+ $value = $dict_entry.Value
+ if ($value -is [Hashtable]) {
+ $snake_dict.$snake_key = Convert-DictToSnakeCase -dict $value
+ }
+ elseif ($value -is [Array] -or $value -is [System.Collections.ArrayList]) {
+ $snake_dict.$snake_key = Convert-ListToSnakeCase -list $value
+ }
+ else {
+ $snake_dict.$snake_key = $value
+ }
+ }
+
+ return , $snake_dict
+}
+
+# this line must stay at the bottom to ensure all defined module parts are exported
+Export-ModuleMember -Alias * -Function * -Cmdlet *
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1
new file mode 100644
index 0000000..56b5d39
--- /dev/null
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1
@@ -0,0 +1,107 @@
+# Copyright (c) 2017 Ansible Project
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+#AnsibleRequires -CSharpUtil Ansible.Process
+
+Function Get-ExecutablePath {
+ <#
+ .SYNOPSIS
+ Get's the full path to an executable, will search the directory specified or ones in the PATH env var.
+
+ .PARAMETER executable
+ [String]The executable to search for.
+
+ .PARAMETER directory
+ [String] If set, the directory to search in.
+
+ .OUTPUT
+ [String] The full path the executable specified.
+ #>
+ Param(
+ [String]$executable,
+ [String]$directory = $null
+ )
+
+ # we need to add .exe if it doesn't have an extension already
+ if (-not [System.IO.Path]::HasExtension($executable)) {
+ $executable = "$($executable).exe"
+ }
+ $full_path = [System.IO.Path]::GetFullPath($executable)
+
+ if ($full_path -ne $executable -and $directory -ne $null) {
+ $file = Get-Item -LiteralPath "$directory\$executable" -Force -ErrorAction SilentlyContinue
+ }
+ else {
+ $file = Get-Item -LiteralPath $executable -Force -ErrorAction SilentlyContinue
+ }
+
+ if ($null -ne $file) {
+ $executable_path = $file.FullName
+ }
+ else {
+ $executable_path = [Ansible.Process.ProcessUtil]::SearchPath($executable)
+ }
+ return $executable_path
+}
+
+Function Run-Command {
+ <#
+ .SYNOPSIS
+ Run a command with the CreateProcess API and return the stdout/stderr and return code.
+
+ .PARAMETER command
+ The full command, including the executable, to run.
+
+ .PARAMETER working_directory
+ The working directory to set on the new process, will default to the current working dir.
+
+ .PARAMETER stdin
+ A string to sent over the stdin pipe to the new process.
+
+ .PARAMETER environment
+ A hashtable of key/value pairs to run with the command. If set, it will replace all other env vars.
+
+ .PARAMETER output_encoding_override
+ The character encoding name for decoding stdout/stderr output of the process.
+
+ .OUTPUT
+ [Hashtable]
+ [String]executable - The full path to the executable that was run
+ [String]stdout - The stdout stream of the process
+ [String]stderr - The stderr stream of the process
+ [Int32]rc - The return code of the process
+ #>
+ Param(
+ [string]$command,
+ [string]$working_directory = $null,
+ [string]$stdin = "",
+ [hashtable]$environment = @{},
+ [string]$output_encoding_override = $null
+ )
+
+ # need to validate the working directory if it is set
+ if ($working_directory) {
+ # validate working directory is a valid path
+ if (-not (Test-Path -LiteralPath $working_directory)) {
+ throw "invalid working directory path '$working_directory'"
+ }
+ }
+
+ # lpApplicationName needs to be the full path to an executable, we do this
+ # by getting the executable as the first arg and then getting the full path
+ $arguments = [Ansible.Process.ProcessUtil]::ParseCommandLine($command)
+ $executable = Get-ExecutablePath -executable $arguments[0] -directory $working_directory
+
+ # run the command and get the results
+ $command_result = [Ansible.Process.ProcessUtil]::CreateProcess($executable, $command, $working_directory, $environment, $stdin, $output_encoding_override)
+
+ return , @{
+ executable = $executable
+ stdout = $command_result.StandardOut
+ stderr = $command_result.StandardError
+ rc = $command_result.ExitCode
+ }
+}
+
+# this line must stay at the bottom to ensure all defined module parts are exported
+Export-ModuleMember -Function Get-ExecutablePath, Run-Command
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.FileUtil.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.FileUtil.psm1
new file mode 100644
index 0000000..cd614d4
--- /dev/null
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.FileUtil.psm1
@@ -0,0 +1,66 @@
+# Copyright (c) 2017 Ansible Project
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+<#
+Test-Path/Get-Item cannot find/return info on files that are locked like
+C:\pagefile.sys. These 2 functions are designed to work with these files and
+provide similar functionality with the normal cmdlets with as minimal overhead
+as possible. They work by using Get-ChildItem with a filter and return the
+result from that.
+#>
+
+Function Test-AnsiblePath {
+ [CmdletBinding()]
+ Param(
+ [Parameter(Mandatory = $true)][string]$Path
+ )
+ # Replacement for Test-Path
+ try {
+ $file_attributes = [System.IO.File]::GetAttributes($Path)
+ }
+ catch [System.IO.FileNotFoundException], [System.IO.DirectoryNotFoundException] {
+ return $false
+ }
+ catch [NotSupportedException] {
+ # When testing a path like Cert:\LocalMachine\My, System.IO.File will
+ # not work, we just revert back to using Test-Path for this
+ return Test-Path -Path $Path
+ }
+
+ if ([Int32]$file_attributes -eq -1) {
+ return $false
+ }
+ else {
+ return $true
+ }
+}
+
+Function Get-AnsibleItem {
+ [CmdletBinding()]
+ Param(
+ [Parameter(Mandatory = $true)][string]$Path
+ )
+ # Replacement for Get-Item
+ try {
+ $file_attributes = [System.IO.File]::GetAttributes($Path)
+ }
+ catch {
+ # if -ErrorAction SilentlyCotinue is set on the cmdlet and we failed to
+ # get the attributes, just return $null, otherwise throw the error
+ if ($ErrorActionPreference -ne "SilentlyContinue") {
+ throw $_
+ }
+ return $null
+ }
+ if ([Int32]$file_attributes -eq -1) {
+ throw New-Object -TypeName System.Management.Automation.ItemNotFoundException -ArgumentList "Cannot find path '$Path' because it does not exist."
+ }
+ elseif ($file_attributes.HasFlag([System.IO.FileAttributes]::Directory)) {
+ return New-Object -TypeName System.IO.DirectoryInfo -ArgumentList $Path
+ }
+ else {
+ return New-Object -TypeName System.IO.FileInfo -ArgumentList $Path
+ }
+}
+
+Export-ModuleMember -Function Test-AnsiblePath, Get-AnsibleItem
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1
new file mode 100644
index 0000000..f0cb440
--- /dev/null
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1
@@ -0,0 +1,390 @@
+# Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2014, and others
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+Set-StrictMode -Version 2.0
+$ErrorActionPreference = "Stop"
+
+Function Set-Attr($obj, $name, $value) {
+ <#
+ .SYNOPSIS
+ Helper function to set an "attribute" on a psobject instance in PowerShell.
+ This is a convenience to make adding Members to the object easier and
+ slightly more pythonic
+ .EXAMPLE
+ Set-Attr $result "changed" $true
+#>
+
+ # If the provided $obj is undefined, define one to be nice
+ If (-not $obj.GetType) {
+ $obj = @{ }
+ }
+
+ Try {
+ $obj.$name = $value
+ }
+ Catch {
+ $obj | Add-Member -Force -MemberType NoteProperty -Name $name -Value $value
+ }
+}
+
+Function Exit-Json($obj) {
+ <#
+ .SYNOPSIS
+ Helper function to convert a PowerShell object to JSON and output it, exiting
+ the script
+ .EXAMPLE
+ Exit-Json $result
+#>
+
+ # If the provided $obj is undefined, define one to be nice
+ If (-not $obj.GetType) {
+ $obj = @{ }
+ }
+
+ if (-not $obj.ContainsKey('changed')) {
+ Set-Attr -obj $obj -name "changed" -value $false
+ }
+
+ Write-Output $obj | ConvertTo-Json -Compress -Depth 99
+ Exit
+}
+
+Function Fail-Json($obj, $message = $null) {
+ <#
+ .SYNOPSIS
+ Helper function to add the "msg" property and "failed" property, convert the
+ PowerShell Hashtable to JSON and output it, exiting the script
+ .EXAMPLE
+ Fail-Json $result "This is the failure message"
+#>
+
+ if ($obj -is [hashtable] -or $obj -is [psobject]) {
+ # Nothing to do
+ }
+ elseif ($obj -is [string] -and $null -eq $message) {
+ # If we weren't given 2 args, and the only arg was a string,
+ # create a new Hashtable and use the arg as the failure message
+ $message = $obj
+ $obj = @{ }
+ }
+ else {
+ # If the first argument is undefined or a different type,
+ # make it a Hashtable
+ $obj = @{ }
+ }
+
+ # Still using Set-Attr for PSObject compatibility
+ Set-Attr -obj $obj -name "msg" -value $message
+ Set-Attr -obj $obj -name "failed" -value $true
+
+ if (-not $obj.ContainsKey('changed')) {
+ Set-Attr -obj $obj -name "changed" -value $false
+ }
+
+ Write-Output $obj | ConvertTo-Json -Compress -Depth 99
+ Exit 1
+}
+
+Function Add-Warning($obj, $message) {
+ <#
+ .SYNOPSIS
+ Helper function to add warnings, even if the warnings attribute was
+ not already set up. This is a convenience for the module developer
+ so they do not have to check for the attribute prior to adding.
+#>
+
+ if (-not $obj.ContainsKey("warnings")) {
+ $obj.warnings = @()
+ }
+ elseif ($obj.warnings -isnot [array]) {
+ throw "Add-Warning: warnings attribute is not an array"
+ }
+
+ $obj.warnings += $message
+}
+
+Function Add-DeprecationWarning($obj, $message, $version = $null) {
+ <#
+ .SYNOPSIS
+ Helper function to add deprecations, even if the deprecations attribute was
+ not already set up. This is a convenience for the module developer
+ so they do not have to check for the attribute prior to adding.
+#>
+ if (-not $obj.ContainsKey("deprecations")) {
+ $obj.deprecations = @()
+ }
+ elseif ($obj.deprecations -isnot [array]) {
+ throw "Add-DeprecationWarning: deprecations attribute is not a list"
+ }
+
+ $obj.deprecations += @{
+ msg = $message
+ version = $version
+ }
+}
+
+Function Expand-Environment($value) {
+ <#
+ .SYNOPSIS
+ Helper function to expand environment variables in values. By default
+ it turns any type to a string, but we ensure $null remains $null.
+#>
+ if ($null -ne $value) {
+ [System.Environment]::ExpandEnvironmentVariables($value)
+ }
+ else {
+ $value
+ }
+}
+
+Function Get-AnsibleParam {
+ <#
+ .SYNOPSIS
+ Helper function to get an "attribute" from a psobject instance in PowerShell.
+ This is a convenience to make getting Members from an object easier and
+ slightly more pythonic
+ .EXAMPLE
+ $attr = Get-AnsibleParam $response "code" -default "1"
+ .EXAMPLE
+ Get-AnsibleParam -obj $params -name "State" -default "Present" -ValidateSet "Present","Absent" -resultobj $resultobj -failifempty $true
+ Get-AnsibleParam also supports Parameter validation to save you from coding that manually
+ Note that if you use the failifempty option, you do need to specify resultobject as well.
+#>
+ param (
+ $obj,
+ $name,
+ $default = $null,
+ $resultobj = @{},
+ $failifempty = $false,
+ $emptyattributefailmessage,
+ $ValidateSet,
+ $ValidateSetErrorMessage,
+ $type = $null,
+ $aliases = @()
+ )
+ # Check if the provided Member $name or aliases exist in $obj and return it or the default.
+ try {
+
+ $found = $null
+ # First try to find preferred parameter $name
+ $aliases = @($name) + $aliases
+
+ # Iterate over aliases to find acceptable Member $name
+ foreach ($alias in $aliases) {
+ if ($obj.ContainsKey($alias)) {
+ $found = $alias
+ break
+ }
+ }
+
+ if ($null -eq $found) {
+ throw
+ }
+ $name = $found
+
+ if ($ValidateSet) {
+
+ if ($ValidateSet -contains ($obj.$name)) {
+ $value = $obj.$name
+ }
+ else {
+ if ($null -eq $ValidateSetErrorMessage) {
+ #Auto-generated error should be sufficient in most use cases
+ $ValidateSetErrorMessage = "Get-AnsibleParam: Argument $name needs to be one of $($ValidateSet -join ",") but was $($obj.$name)."
+ }
+ Fail-Json -obj $resultobj -message $ValidateSetErrorMessage
+ }
+ }
+ else {
+ $value = $obj.$name
+ }
+ }
+ catch {
+ if ($failifempty -eq $false) {
+ $value = $default
+ }
+ else {
+ if (-not $emptyattributefailmessage) {
+ $emptyattributefailmessage = "Get-AnsibleParam: Missing required argument: $name"
+ }
+ Fail-Json -obj $resultobj -message $emptyattributefailmessage
+ }
+ }
+
+ # If $null -eq $value, the parameter was unspecified by the user (deliberately or not)
+ # Please leave $null-values intact, modules need to know if a parameter was specified
+ if ($null -eq $value) {
+ return $null
+ }
+
+ if ($type -eq "path") {
+ # Expand environment variables on path-type
+ $value = Expand-Environment($value)
+ # Test if a valid path is provided
+ if (-not (Test-Path -IsValid $value)) {
+ $path_invalid = $true
+ # could still be a valid-shaped path with a nonexistent drive letter
+ if ($value -match "^\w:") {
+ # rewrite path with a valid drive letter and recheck the shape- this might still fail, eg, a nonexistent non-filesystem PS path
+ if (Test-Path -IsValid $(@(Get-PSDrive -PSProvider Filesystem)[0].Name + $value.Substring(1))) {
+ $path_invalid = $false
+ }
+ }
+ if ($path_invalid) {
+ Fail-Json -obj $resultobj -message "Get-AnsibleParam: Parameter '$name' has an invalid path '$value' specified."
+ }
+ }
+ }
+ elseif ($type -eq "str") {
+ # Convert str types to real Powershell strings
+ $value = $value.ToString()
+ }
+ elseif ($type -eq "bool") {
+ # Convert boolean types to real Powershell booleans
+ $value = $value | ConvertTo-Bool
+ }
+ elseif ($type -eq "int") {
+ # Convert int types to real Powershell integers
+ $value = $value -as [int]
+ }
+ elseif ($type -eq "float") {
+ # Convert float types to real Powershell floats
+ $value = $value -as [float]
+ }
+ elseif ($type -eq "list") {
+ if ($value -is [array]) {
+ # Nothing to do
+ }
+ elseif ($value -is [string]) {
+ # Convert string type to real Powershell array
+ $value = $value.Split(",").Trim()
+ }
+ elseif ($value -is [int]) {
+ $value = @($value)
+ }
+ else {
+ Fail-Json -obj $resultobj -message "Get-AnsibleParam: Parameter '$name' is not a YAML list."
+ }
+ # , is not a typo, forces it to return as a list when it is empty or only has 1 entry
+ return , $value
+ }
+
+ return $value
+}
+
+#Alias Get-attr-->Get-AnsibleParam for backwards compat. Only add when needed to ease debugging of scripts
+If (-not(Get-Alias -Name "Get-attr" -ErrorAction SilentlyContinue)) {
+ New-Alias -Name Get-attr -Value Get-AnsibleParam
+}
+
+Function ConvertTo-Bool {
+ <#
+ .SYNOPSIS
+ Helper filter/pipeline function to convert a value to boolean following current
+ Ansible practices
+ .EXAMPLE
+ $is_true = "true" | ConvertTo-Bool
+#>
+ param(
+ [parameter(valuefrompipeline = $true)]
+ $obj
+ )
+
+ process {
+ $boolean_strings = "yes", "on", "1", "true", 1
+ $obj_string = [string]$obj
+
+ if (($obj -is [boolean] -and $obj) -or $boolean_strings -contains $obj_string.ToLower()) {
+ return $true
+ }
+ else {
+ return $false
+ }
+ }
+}
+
+Function Parse-Args {
+ <#
+ .SYNOPSIS
+ Helper function to parse Ansible JSON arguments from a "file" passed as
+ the single argument to the module.
+ .EXAMPLE
+ $params = Parse-Args $args
+#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "Cannot change the name now")]
+ param ($arguments, $supports_check_mode = $false)
+
+ $params = New-Object psobject
+ If ($arguments.Length -gt 0) {
+ $params = Get-Content $arguments[0] | ConvertFrom-Json
+ }
+ Else {
+ $params = $complex_args
+ }
+ $check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
+ If ($check_mode -and -not $supports_check_mode) {
+ Exit-Json @{
+ skipped = $true
+ changed = $false
+ msg = "remote module does not support check mode"
+ }
+ }
+ return $params
+}
+
+
+Function Get-FileChecksum($path, $algorithm = 'sha1') {
+ <#
+ .SYNOPSIS
+ Helper function to calculate a hash of a file in a way which PowerShell 3
+ and above can handle
+#>
+ If (Test-Path -LiteralPath $path -PathType Leaf) {
+ 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 @{} "Unsupported hash algorithm supplied '$algorithm'" }
+ }
+
+ If ($PSVersionTable.PSVersion.Major -ge 4) {
+ $raw_hash = Get-FileHash -LiteralPath $path -Algorithm $algorithm
+ $hash = $raw_hash.Hash.ToLower()
+ }
+ Else {
+ $fp = [System.IO.File]::Open($path, [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite);
+ $hash = [System.BitConverter]::ToString($sp.ComputeHash($fp)).Replace("-", "").ToLower();
+ $fp.Dispose();
+ }
+ }
+ ElseIf (Test-Path -LiteralPath $path -PathType Container) {
+ $hash = "3";
+ }
+ Else {
+ $hash = "1";
+ }
+ return $hash
+}
+
+Function Get-PendingRebootStatus {
+ <#
+ .SYNOPSIS
+ Check if reboot is required, if so notify CA.
+ Function returns true if computer has a pending reboot
+#>
+ $featureData = Invoke-CimMethod -EA Ignore -Name GetServerFeature -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks
+ $regData = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" "PendingFileRenameOperations" -EA Ignore
+ $CBSRebootStatus = Get-ChildItem "HKLM:\\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing" -ErrorAction SilentlyContinue |
+ Where-Object { $_.PSChildName -eq "RebootPending" }
+ if (($featureData -and $featureData.RequiresReboot) -or $regData -or $CBSRebootStatus) {
+ return $True
+ }
+ else {
+ return $False
+ }
+}
+
+# this line must stay at the bottom to ensure all defined module parts are exported
+Export-ModuleMember -Alias * -Function * -Cmdlet *
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.LinkUtil.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.LinkUtil.psm1
new file mode 100644
index 0000000..1a251f6
--- /dev/null
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.LinkUtil.psm1
@@ -0,0 +1,464 @@
+# Copyright (c) 2017 Ansible Project
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+#Requires -Module Ansible.ModuleUtils.PrivilegeUtil
+
+Function Load-LinkUtils {
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "Cannot change the name now")]
+ param ()
+
+ $link_util = @'
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ansible
+{
+ public enum LinkType
+ {
+ SymbolicLink,
+ JunctionPoint,
+ HardLink
+ }
+
+ public class LinkUtilWin32Exception : System.ComponentModel.Win32Exception
+ {
+ private string _msg;
+
+ public LinkUtilWin32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
+
+ public LinkUtilWin32Exception(int errorCode, string message) : base(errorCode)
+ {
+ _msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode);
+ }
+
+ public override string Message { get { return _msg; } }
+ public static explicit operator LinkUtilWin32Exception(string message) { return new LinkUtilWin32Exception(message); }
+ }
+
+ public class LinkInfo
+ {
+ public LinkType Type { get; internal set; }
+ public string PrintName { get; internal set; }
+ public string SubstituteName { get; internal set; }
+ public string AbsolutePath { get; internal set; }
+ public string TargetPath { get; internal set; }
+ public string[] HardTargets { get; internal set; }
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ public struct REPARSE_DATA_BUFFER
+ {
+ public UInt32 ReparseTag;
+ public UInt16 ReparseDataLength;
+ public UInt16 Reserved;
+ public UInt16 SubstituteNameOffset;
+ public UInt16 SubstituteNameLength;
+ public UInt16 PrintNameOffset;
+ public UInt16 PrintNameLength;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = LinkUtil.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)]
+ public char[] PathBuffer;
+ }
+
+ public class LinkUtil
+ {
+ public const int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 1024 * 16;
+
+ private const UInt32 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
+ private const UInt32 FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;
+
+ private const UInt32 FSCTL_GET_REPARSE_POINT = 0x000900A8;
+ private const UInt32 FSCTL_SET_REPARSE_POINT = 0x000900A4;
+ private const UInt32 FILE_DEVICE_FILE_SYSTEM = 0x00090000;
+
+ private const UInt32 IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003;
+ private const UInt32 IO_REPARSE_TAG_SYMLINK = 0xA000000C;
+
+ private const UInt32 SYMLINK_FLAG_RELATIVE = 0x00000001;
+
+ private const Int64 INVALID_HANDLE_VALUE = -1;
+
+ private const UInt32 SIZE_OF_WCHAR = 2;
+
+ private const UInt32 SYMBOLIC_LINK_FLAG_FILE = 0x00000000;
+ private const UInt32 SYMBOLIC_LINK_FLAG_DIRECTORY = 0x00000001;
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
+ private static extern SafeFileHandle CreateFile(
+ string lpFileName,
+ [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
+ [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
+ IntPtr lpSecurityAttributes,
+ [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
+ UInt32 dwFlagsAndAttributes,
+ IntPtr hTemplateFile);
+
+ // Used by GetReparsePointInfo()
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ private static extern bool DeviceIoControl(
+ SafeFileHandle hDevice,
+ UInt32 dwIoControlCode,
+ IntPtr lpInBuffer,
+ UInt32 nInBufferSize,
+ out REPARSE_DATA_BUFFER lpOutBuffer,
+ UInt32 nOutBufferSize,
+ out UInt32 lpBytesReturned,
+ IntPtr lpOverlapped);
+
+ // Used by CreateJunctionPoint()
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ private static extern bool DeviceIoControl(
+ SafeFileHandle hDevice,
+ UInt32 dwIoControlCode,
+ REPARSE_DATA_BUFFER lpInBuffer,
+ UInt32 nInBufferSize,
+ IntPtr lpOutBuffer,
+ UInt32 nOutBufferSize,
+ out UInt32 lpBytesReturned,
+ IntPtr lpOverlapped);
+
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ private static extern bool GetVolumePathName(
+ string lpszFileName,
+ StringBuilder lpszVolumePathName,
+ ref UInt32 cchBufferLength);
+
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ private static extern IntPtr FindFirstFileNameW(
+ string lpFileName,
+ UInt32 dwFlags,
+ ref UInt32 StringLength,
+ StringBuilder LinkName);
+
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ private static extern bool FindNextFileNameW(
+ IntPtr hFindStream,
+ ref UInt32 StringLength,
+ StringBuilder LinkName);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ private static extern bool FindClose(
+ IntPtr hFindFile);
+
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ private static extern bool RemoveDirectory(
+ string lpPathName);
+
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ private static extern bool DeleteFile(
+ string lpFileName);
+
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ private static extern bool CreateSymbolicLink(
+ string lpSymlinkFileName,
+ string lpTargetFileName,
+ UInt32 dwFlags);
+
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ private static extern bool CreateHardLink(
+ string lpFileName,
+ string lpExistingFileName,
+ IntPtr lpSecurityAttributes);
+
+ public static LinkInfo GetLinkInfo(string linkPath)
+ {
+ FileAttributes attr = File.GetAttributes(linkPath);
+ if (attr.HasFlag(FileAttributes.ReparsePoint))
+ return GetReparsePointInfo(linkPath);
+
+ if (!attr.HasFlag(FileAttributes.Directory))
+ return GetHardLinkInfo(linkPath);
+
+ return null;
+ }
+
+ public static void DeleteLink(string linkPath)
+ {
+ bool success;
+ FileAttributes attr = File.GetAttributes(linkPath);
+ if (attr.HasFlag(FileAttributes.Directory))
+ {
+ success = RemoveDirectory(linkPath);
+ }
+ else
+ {
+ success = DeleteFile(linkPath);
+ }
+
+ if (!success)
+ throw new LinkUtilWin32Exception(String.Format("Failed to delete link at {0}", linkPath));
+ }
+
+ public static void CreateLink(string linkPath, String linkTarget, LinkType linkType)
+ {
+ switch (linkType)
+ {
+ case LinkType.SymbolicLink:
+ UInt32 linkFlags;
+ FileAttributes attr = File.GetAttributes(linkTarget);
+ if (attr.HasFlag(FileAttributes.Directory))
+ linkFlags = SYMBOLIC_LINK_FLAG_DIRECTORY;
+ else
+ linkFlags = SYMBOLIC_LINK_FLAG_FILE;
+
+ if (!CreateSymbolicLink(linkPath, linkTarget, linkFlags))
+ throw new LinkUtilWin32Exception(String.Format("CreateSymbolicLink({0}, {1}, {2}) failed", linkPath, linkTarget, linkFlags));
+ break;
+ case LinkType.JunctionPoint:
+ CreateJunctionPoint(linkPath, linkTarget);
+ break;
+ case LinkType.HardLink:
+ if (!CreateHardLink(linkPath, linkTarget, IntPtr.Zero))
+ throw new LinkUtilWin32Exception(String.Format("CreateHardLink({0}, {1}) failed", linkPath, linkTarget));
+ break;
+ }
+ }
+
+ private static LinkInfo GetHardLinkInfo(string linkPath)
+ {
+ UInt32 maxPath = 260;
+ List<string> result = new List<string>();
+
+ StringBuilder sb = new StringBuilder((int)maxPath);
+ UInt32 stringLength = maxPath;
+ if (!GetVolumePathName(linkPath, sb, ref stringLength))
+ throw new LinkUtilWin32Exception("GetVolumePathName() failed");
+ string volume = sb.ToString();
+
+ stringLength = maxPath;
+ IntPtr findHandle = FindFirstFileNameW(linkPath, 0, ref stringLength, sb);
+ if (findHandle.ToInt64() != INVALID_HANDLE_VALUE)
+ {
+ try
+ {
+ do
+ {
+ string hardLinkPath = sb.ToString();
+ if (hardLinkPath.StartsWith("\\"))
+ hardLinkPath = hardLinkPath.Substring(1, hardLinkPath.Length - 1);
+
+ result.Add(Path.Combine(volume, hardLinkPath));
+ stringLength = maxPath;
+
+ } while (FindNextFileNameW(findHandle, ref stringLength, sb));
+ }
+ finally
+ {
+ FindClose(findHandle);
+ }
+ }
+
+ if (result.Count > 1)
+ return new LinkInfo
+ {
+ Type = LinkType.HardLink,
+ HardTargets = result.ToArray()
+ };
+
+ return null;
+ }
+
+ private static LinkInfo GetReparsePointInfo(string linkPath)
+ {
+ SafeFileHandle fileHandle = CreateFile(
+ linkPath,
+ FileAccess.Read,
+ FileShare.None,
+ IntPtr.Zero,
+ FileMode.Open,
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
+ IntPtr.Zero);
+
+ if (fileHandle.IsInvalid)
+ throw new LinkUtilWin32Exception(String.Format("CreateFile({0}) failed", linkPath));
+
+ REPARSE_DATA_BUFFER buffer = new REPARSE_DATA_BUFFER();
+ UInt32 bytesReturned;
+ try
+ {
+ if (!DeviceIoControl(
+ fileHandle,
+ FSCTL_GET_REPARSE_POINT,
+ IntPtr.Zero,
+ 0,
+ out buffer,
+ MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
+ out bytesReturned,
+ IntPtr.Zero))
+ throw new LinkUtilWin32Exception(String.Format("DeviceIoControl() failed for file at {0}", linkPath));
+ }
+ finally
+ {
+ fileHandle.Dispose();
+ }
+
+ bool isRelative = false;
+ int pathOffset = 0;
+ LinkType linkType;
+ if (buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK)
+ {
+ UInt32 bufferFlags = Convert.ToUInt32(buffer.PathBuffer[0]) + Convert.ToUInt32(buffer.PathBuffer[1]);
+ if (bufferFlags == SYMLINK_FLAG_RELATIVE)
+ isRelative = true;
+ pathOffset = 2;
+ linkType = LinkType.SymbolicLink;
+ }
+ else if (buffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
+ {
+ linkType = LinkType.JunctionPoint;
+ }
+ else
+ {
+ string errorMessage = String.Format("Invalid Reparse Tag: {0}", buffer.ReparseTag.ToString());
+ throw new Exception(errorMessage);
+ }
+
+ string printName = new string(buffer.PathBuffer,
+ (int)(buffer.PrintNameOffset / SIZE_OF_WCHAR) + pathOffset,
+ (int)(buffer.PrintNameLength / SIZE_OF_WCHAR));
+ string substituteName = new string(buffer.PathBuffer,
+ (int)(buffer.SubstituteNameOffset / SIZE_OF_WCHAR) + pathOffset,
+ (int)(buffer.SubstituteNameLength / SIZE_OF_WCHAR));
+
+ // TODO: should we check for \?\UNC\server for convert it to the NT style \\server path
+ // Remove the leading Windows object directory \?\ from the path if present
+ string targetPath = substituteName;
+ if (targetPath.StartsWith("\\??\\"))
+ targetPath = targetPath.Substring(4, targetPath.Length - 4);
+
+ string absolutePath = targetPath;
+ if (isRelative)
+ absolutePath = Path.GetFullPath(Path.Combine(new FileInfo(linkPath).Directory.FullName, targetPath));
+
+ return new LinkInfo
+ {
+ Type = linkType,
+ PrintName = printName,
+ SubstituteName = substituteName,
+ AbsolutePath = absolutePath,
+ TargetPath = targetPath
+ };
+ }
+
+ private static void CreateJunctionPoint(string linkPath, string linkTarget)
+ {
+ // We need to create the link as a dir beforehand
+ Directory.CreateDirectory(linkPath);
+ SafeFileHandle fileHandle = CreateFile(
+ linkPath,
+ FileAccess.Write,
+ FileShare.Read | FileShare.Write | FileShare.None,
+ IntPtr.Zero,
+ FileMode.Open,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
+ IntPtr.Zero);
+
+ if (fileHandle.IsInvalid)
+ throw new LinkUtilWin32Exception(String.Format("CreateFile({0}) failed", linkPath));
+
+ try
+ {
+ string substituteName = "\\??\\" + Path.GetFullPath(linkTarget);
+ string printName = linkTarget;
+
+ REPARSE_DATA_BUFFER buffer = new REPARSE_DATA_BUFFER();
+ buffer.SubstituteNameOffset = 0;
+ buffer.SubstituteNameLength = (UInt16)(substituteName.Length * SIZE_OF_WCHAR);
+ buffer.PrintNameOffset = (UInt16)(buffer.SubstituteNameLength + 2);
+ buffer.PrintNameLength = (UInt16)(printName.Length * SIZE_OF_WCHAR);
+
+ buffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
+ buffer.ReparseDataLength = (UInt16)(buffer.SubstituteNameLength + buffer.PrintNameLength + 12);
+ buffer.PathBuffer = new char[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
+
+ byte[] unicodeBytes = Encoding.Unicode.GetBytes(substituteName + "\0" + printName);
+ char[] pathBuffer = Encoding.Unicode.GetChars(unicodeBytes);
+ Array.Copy(pathBuffer, buffer.PathBuffer, pathBuffer.Length);
+
+ UInt32 bytesReturned;
+ if (!DeviceIoControl(
+ fileHandle,
+ FSCTL_SET_REPARSE_POINT,
+ buffer,
+ (UInt32)(buffer.ReparseDataLength + 8),
+ IntPtr.Zero, 0,
+ out bytesReturned,
+ IntPtr.Zero))
+ throw new LinkUtilWin32Exception(String.Format("DeviceIoControl() failed to create junction point at {0} to {1}", linkPath, linkTarget));
+ }
+ finally
+ {
+ fileHandle.Dispose();
+ }
+ }
+ }
+}
+'@
+
+ # FUTURE: find a better way to get the _ansible_remote_tmp variable
+ $original_tmp = $env:TMP
+ $original_lib = $env:LIB
+
+ $remote_tmp = $original_tmp
+ $module_params = Get-Variable -Name complex_args -ErrorAction SilentlyContinue
+ if ($module_params) {
+ if ($module_params.Value.ContainsKey("_ansible_remote_tmp") ) {
+ $remote_tmp = $module_params.Value["_ansible_remote_tmp"]
+ $remote_tmp = [System.Environment]::ExpandEnvironmentVariables($remote_tmp)
+ }
+ }
+
+ $env:TMP = $remote_tmp
+ $env:LIB = $null
+ Add-Type -TypeDefinition $link_util
+ $env:TMP = $original_tmp
+ $env:LIB = $original_lib
+
+ # enable the SeBackupPrivilege if it is disabled
+ $state = Get-AnsiblePrivilege -Name SeBackupPrivilege
+ if ($state -eq $false) {
+ Set-AnsiblePrivilege -Name SeBackupPrivilege -Value $true
+ }
+}
+
+Function Get-Link($link_path) {
+ $link_info = [Ansible.LinkUtil]::GetLinkInfo($link_path)
+ return $link_info
+}
+
+Function Remove-Link($link_path) {
+ [Ansible.LinkUtil]::DeleteLink($link_path)
+}
+
+Function New-Link($link_path, $link_target, $link_type) {
+ if (-not (Test-Path -LiteralPath $link_target)) {
+ throw "link_target '$link_target' does not exist, cannot create link"
+ }
+
+ switch ($link_type) {
+ "link" {
+ $type = [Ansible.LinkType]::SymbolicLink
+ }
+ "junction" {
+ if (Test-Path -LiteralPath $link_target -PathType Leaf) {
+ throw "cannot set the target for a junction point to a file"
+ }
+ $type = [Ansible.LinkType]::JunctionPoint
+ }
+ "hard" {
+ if (Test-Path -LiteralPath $link_target -PathType Container) {
+ throw "cannot set the target for a hard link to a directory"
+ }
+ $type = [Ansible.LinkType]::HardLink
+ }
+ default { throw "invalid link_type option $($link_type): expecting link, junction, hard" }
+ }
+ [Ansible.LinkUtil]::CreateLink($link_path, $link_target, $type)
+}
+
+# this line must stay at the bottom to ensure all defined module parts are exported
+Export-ModuleMember -Alias * -Function * -Cmdlet *
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.PrivilegeUtil.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.PrivilegeUtil.psm1
new file mode 100644
index 0000000..78f0d64
--- /dev/null
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.PrivilegeUtil.psm1
@@ -0,0 +1,83 @@
+# Copyright (c) 2018 Ansible Project
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+#AnsibleRequires -CSharpUtil Ansible.Privilege
+
+Function Get-AnsiblePrivilege {
+ <#
+ .SYNOPSIS
+ Get the status of a privilege for the current process. This returns
+ $true - the privilege is enabled
+ $false - the privilege is disabled
+ $null - the privilege is removed from the token
+
+ If Name is not a valid privilege name, this will throw an
+ ArgumentException.
+
+ .EXAMPLE
+ Get-AnsiblePrivilege -Name SeDebugPrivilege
+ #>
+ [CmdletBinding()]
+ param(
+ [Parameter(Mandatory = $true)][String]$Name
+ )
+
+ if (-not [Ansible.Privilege.PrivilegeUtil]::CheckPrivilegeName($Name)) {
+ throw [System.ArgumentException] "Invalid privilege name '$Name'"
+ }
+
+ $process_token = [Ansible.Privilege.PrivilegeUtil]::GetCurrentProcess()
+ $privilege_info = [Ansible.Privilege.PrivilegeUtil]::GetAllPrivilegeInfo($process_token)
+ if ($privilege_info.ContainsKey($Name)) {
+ $status = $privilege_info.$Name
+ return $status.HasFlag([Ansible.Privilege.PrivilegeAttributes]::Enabled)
+ }
+ else {
+ return $null
+ }
+}
+
+Function Set-AnsiblePrivilege {
+ <#
+ .SYNOPSIS
+ Enables/Disables a privilege on the current process' token. If a privilege
+ has been removed from the process token, this will throw an
+ InvalidOperationException.
+
+ .EXAMPLE
+ # enable a privilege
+ Set-AnsiblePrivilege -Name SeCreateSymbolicLinkPrivilege -Value $true
+
+ # disable a privilege
+ Set-AnsiblePrivilege -Name SeCreateSymbolicLinkPrivilege -Value $false
+ #>
+ [CmdletBinding(SupportsShouldProcess)]
+ param(
+ [Parameter(Mandatory = $true)][String]$Name,
+ [Parameter(Mandatory = $true)][bool]$Value
+ )
+
+ $action = switch ($Value) {
+ $true { "Enable" }
+ $false { "Disable" }
+ }
+
+ $current_state = Get-AnsiblePrivilege -Name $Name
+ if ($current_state -eq $Value) {
+ return # no change needs to occur
+ }
+ elseif ($null -eq $current_state) {
+ # once a privilege is removed from a token we cannot do anything with it
+ throw [System.InvalidOperationException] "Cannot $($action.ToLower()) the privilege '$Name' as it has been removed from the token"
+ }
+
+ $process_token = [Ansible.Privilege.PrivilegeUtil]::GetCurrentProcess()
+ if ($PSCmdlet.ShouldProcess($Name, "$action the privilege $Name")) {
+ $new_state = New-Object -TypeName 'System.Collections.Generic.Dictionary`2[[System.String], [System.Nullable`1[System.Boolean]]]'
+ $new_state.Add($Name, $Value)
+ [Ansible.Privilege.PrivilegeUtil]::SetTokenPrivileges($process_token, $new_state) > $null
+ }
+}
+
+Export-ModuleMember -Function Get-AnsiblePrivilege, Set-AnsiblePrivilege
+
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.SID.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.SID.psm1
new file mode 100644
index 0000000..d1f4b62
--- /dev/null
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.SID.psm1
@@ -0,0 +1,99 @@
+# Copyright (c) 2017 Ansible Project
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+Function Convert-FromSID($sid) {
+ # Converts a SID to a Down-Level Logon name in the form of DOMAIN\UserName
+ # If the SID is for a local user or group then DOMAIN would be the server
+ # name.
+
+ $account_object = New-Object System.Security.Principal.SecurityIdentifier($sid)
+ try {
+ $nt_account = $account_object.Translate([System.Security.Principal.NTAccount])
+ }
+ catch {
+ Fail-Json -obj @{} -message "failed to convert sid '$sid' to a logon name: $($_.Exception.Message)"
+ }
+
+ return $nt_account.Value
+}
+
+Function Convert-ToSID {
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingEmptyCatchBlock", "",
+ Justification = "We don't care if converting to a SID fails, just that it failed or not")]
+ param($account_name)
+ # Converts an account name to a SID, it can take in the following forms
+ # SID: Will just return the SID value that was passed in
+ # UPN:
+ # principal@domain (Domain users only)
+ # Down-Level Login Name
+ # DOMAIN\principal (Domain)
+ # SERVERNAME\principal (Local)
+ # .\principal (Local)
+ # NT AUTHORITY\SYSTEM (Local Service Accounts)
+ # Login Name
+ # principal (Local/Local Service Accounts)
+
+ try {
+ $sid = New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList $account_name
+ return $sid.Value
+ }
+ catch {}
+
+ if ($account_name -like "*\*") {
+ $account_name_split = $account_name -split "\\"
+ if ($account_name_split[0] -eq ".") {
+ $domain = $env:COMPUTERNAME
+ }
+ else {
+ $domain = $account_name_split[0]
+ }
+ $username = $account_name_split[1]
+ }
+ else {
+ $domain = $null
+ $username = $account_name
+ }
+
+ if ($domain) {
+ # searching for a local group with the servername prefixed will fail,
+ # need to check for this situation and only use NTAccount(String)
+ if ($domain -eq $env:COMPUTERNAME) {
+ $adsi = [ADSI]("WinNT://$env:COMPUTERNAME,computer")
+ $group = $adsi.psbase.children | Where-Object { $_.schemaClassName -eq "group" -and $_.Name -eq $username }
+ }
+ else {
+ $group = $null
+ }
+ if ($group) {
+ $account = New-Object System.Security.Principal.NTAccount($username)
+ }
+ else {
+ $account = New-Object System.Security.Principal.NTAccount($domain, $username)
+ }
+ }
+ else {
+ # when in a domain NTAccount(String) will favour domain lookups check
+ # if username is a local user and explicitly search on the localhost for
+ # that account
+ $adsi = [ADSI]("WinNT://$env:COMPUTERNAME,computer")
+ $user = $adsi.psbase.children | Where-Object { $_.schemaClassName -eq "user" -and $_.Name -eq $username }
+ if ($user) {
+ $account = New-Object System.Security.Principal.NTAccount($env:COMPUTERNAME, $username)
+ }
+ else {
+ $account = New-Object System.Security.Principal.NTAccount($username)
+ }
+ }
+
+ try {
+ $account_sid = $account.Translate([System.Security.Principal.SecurityIdentifier])
+ }
+ catch {
+ Fail-Json @{} "account_name $account_name is not a valid account, cannot get SID: $($_.Exception.Message)"
+ }
+
+ return $account_sid.Value
+}
+
+# this line must stay at the bottom to ensure all defined module parts are exported
+Export-ModuleMember -Alias * -Function * -Cmdlet *
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1
new file mode 100644
index 0000000..b59ba72
--- /dev/null
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1
@@ -0,0 +1,530 @@
+# Copyright (c) 2019 Ansible Project
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+Function Get-AnsibleWebRequest {
+ <#
+ .SYNOPSIS
+ Creates a System.Net.WebRequest object based on common URL module options in Ansible.
+
+ .DESCRIPTION
+ Will create a WebRequest based on common input options within Ansible. This can be used manually or with
+ Invoke-WithWebRequest.
+
+ .PARAMETER Uri
+ The URI to create the web request for.
+
+ .PARAMETER Method
+ The protocol method to use, if omitted, will use the default value for the URI protocol specified.
+
+ .PARAMETER FollowRedirects
+ Whether to follow redirect reponses. This is only valid when using a HTTP URI.
+ all - Will follow all redirects
+ none - Will follow no redirects
+ safe - Will only follow redirects when GET or HEAD is used as the Method
+
+ .PARAMETER Headers
+ A hashtable or dictionary of header values to set on the request. This is only valid for a HTTP URI.
+
+ .PARAMETER HttpAgent
+ A string to set for the 'User-Agent' header. This is only valid for a HTTP URI.
+
+ .PARAMETER MaximumRedirection
+ The maximum number of redirections that will be followed. This is only valid for a HTTP URI.
+
+ .PARAMETER Timeout
+ The timeout in seconds that defines how long to wait until the request times out.
+
+ .PARAMETER ValidateCerts
+ Whether to validate SSL certificates, default to True.
+
+ .PARAMETER ClientCert
+ The path to PFX file to use for X509 authentication. This is only valid for a HTTP URI. This path can either
+ be a filesystem path (C:\folder\cert.pfx) or a PSPath to a credential (Cert:\CurrentUser\My\<thumbprint>).
+
+ .PARAMETER ClientCertPassword
+ The password for the PFX certificate if required. This is only valid for a HTTP URI.
+
+ .PARAMETER ForceBasicAuth
+ Whether to set the Basic auth header on the first request instead of when required. This is only valid for a
+ HTTP URI.
+
+ .PARAMETER UrlUsername
+ The username to use for authenticating with the target.
+
+ .PARAMETER UrlPassword
+ The password to use for authenticating with the target.
+
+ .PARAMETER UseDefaultCredential
+ Whether to use the current user's credentials if available. This will only work when using Become, using SSH with
+ password auth, or WinRM with CredSSP or Kerberos with credential delegation.
+
+ .PARAMETER UseProxy
+ Whether to use the default proxy defined in IE (WinINet) for the user or set no proxy at all. This should not
+ be set to True when ProxyUrl is also defined.
+
+ .PARAMETER ProxyUrl
+ An explicit proxy server to use for the request instead of relying on the default proxy in IE. This is only
+ valid for a HTTP URI.
+
+ .PARAMETER ProxyUsername
+ An optional username to use for proxy authentication.
+
+ .PARAMETER ProxyPassword
+ The password for ProxyUsername.
+
+ .PARAMETER ProxyUseDefaultCredential
+ Whether to use the current user's credentials for proxy authentication if available. This will only work when
+ using Become, using SSH with password auth, or WinRM with CredSSP or Kerberos with credential delegation.
+
+ .PARAMETER Module
+ The AnsibleBasic module that can be used as a backup parameter source or a way to return warnings back to the
+ Ansible controller.
+
+ .EXAMPLE
+ $spec = @{
+ options = @{}
+ }
+ $module = Ansible.Basic.AnsibleModule]::Create($args, $spec, @(Get-AnsibleWebRequestSpec))
+
+ $web_request = Get-AnsibleWebRequest -Module $module
+ #>
+ [CmdletBinding()]
+ [OutputType([System.Net.WebRequest])]
+ Param (
+ [Alias("url")]
+ [System.Uri]
+ $Uri,
+
+ [System.String]
+ $Method,
+
+ [Alias("follow_redirects")]
+ [ValidateSet("all", "none", "safe")]
+ [System.String]
+ $FollowRedirects = "safe",
+
+ [System.Collections.IDictionary]
+ $Headers,
+
+ [Alias("http_agent")]
+ [System.String]
+ $HttpAgent = "ansible-httpget",
+
+ [Alias("maximum_redirection")]
+ [System.Int32]
+ $MaximumRedirection = 50,
+
+ [System.Int32]
+ $Timeout = 30,
+
+ [Alias("validate_certs")]
+ [System.Boolean]
+ $ValidateCerts = $true,
+
+ # Credential params
+ [Alias("client_cert")]
+ [System.String]
+ $ClientCert,
+
+ [Alias("client_cert_password")]
+ [System.String]
+ $ClientCertPassword,
+
+ [Alias("force_basic_auth")]
+ [Switch]
+ $ForceBasicAuth,
+
+ [Alias("url_username")]
+ [System.String]
+ $UrlUsername,
+
+ [Alias("url_password")]
+ [System.String]
+ $UrlPassword,
+
+ [Alias("use_default_credential")]
+ [Switch]
+ $UseDefaultCredential,
+
+ # Proxy params
+ [Alias("use_proxy")]
+ [System.Boolean]
+ $UseProxy = $true,
+
+ [Alias("proxy_url")]
+ [System.String]
+ $ProxyUrl,
+
+ [Alias("proxy_username")]
+ [System.String]
+ $ProxyUsername,
+
+ [Alias("proxy_password")]
+ [System.String]
+ $ProxyPassword,
+
+ [Alias("proxy_use_default_credential")]
+ [Switch]
+ $ProxyUseDefaultCredential,
+
+ [ValidateScript({ $_.GetType().FullName -eq 'Ansible.Basic.AnsibleModule' })]
+ [System.Object]
+ $Module
+ )
+
+ # Set module options for parameters unless they were explicitly passed in.
+ if ($Module) {
+ foreach ($param in $PSCmdlet.MyInvocation.MyCommand.Parameters.GetEnumerator()) {
+ if ($PSBoundParameters.ContainsKey($param.Key)) {
+ # Was set explicitly we want to use that value
+ continue
+ }
+
+ foreach ($alias in @($Param.Key) + $param.Value.Aliases) {
+ if ($Module.Params.ContainsKey($alias)) {
+ $var_value = $Module.Params.$alias -as $param.Value.ParameterType
+ Set-Variable -Name $param.Key -Value $var_value
+ break
+ }
+ }
+ }
+ }
+
+ # Disable certificate validation if requested
+ # FUTURE: set this on ServerCertificateValidationCallback of the HttpWebRequest once .NET 4.5 is the minimum
+ if (-not $ValidateCerts) {
+ [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
+ }
+
+ # Enable TLS1.1/TLS1.2 if they're available but disabled (eg. .NET 4.5)
+ $security_protocols = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::SystemDefault
+ if ([System.Net.SecurityProtocolType].GetMember("Tls11").Count -gt 0) {
+ $security_protocols = $security_protocols -bor [System.Net.SecurityProtocolType]::Tls11
+ }
+ if ([System.Net.SecurityProtocolType].GetMember("Tls12").Count -gt 0) {
+ $security_protocols = $security_protocols -bor [System.Net.SecurityProtocolType]::Tls12
+ }
+ [System.Net.ServicePointManager]::SecurityProtocol = $security_protocols
+
+ $web_request = [System.Net.WebRequest]::Create($Uri)
+ if ($Method) {
+ $web_request.Method = $Method
+ }
+ $web_request.Timeout = $Timeout * 1000
+
+ if ($UseDefaultCredential -and $web_request -is [System.Net.HttpWebRequest]) {
+ $web_request.UseDefaultCredentials = $true
+ }
+ elseif ($UrlUsername) {
+ if ($ForceBasicAuth) {
+ $auth_value = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $UrlUsername, $UrlPassword)))
+ $web_request.Headers.Add("Authorization", "Basic $auth_value")
+ }
+ else {
+ $credential = New-Object -TypeName System.Net.NetworkCredential -ArgumentList $UrlUsername, $UrlPassword
+ $web_request.Credentials = $credential
+ }
+ }
+
+ if ($ClientCert) {
+ # Expecting either a filepath or PSPath (Cert:\CurrentUser\My\<thumbprint>)
+ $cert = Get-Item -LiteralPath $ClientCert -ErrorAction SilentlyContinue
+ if ($null -eq $cert) {
+ Write-Error -Message "Client certificate '$ClientCert' does not exist" -Category ObjectNotFound
+ return
+ }
+
+ $crypto_ns = 'System.Security.Cryptography.X509Certificates'
+ if ($cert.PSProvider.Name -ne 'Certificate') {
+ try {
+ $cert = New-Object -TypeName "$crypto_ns.X509Certificate2" -ArgumentList @(
+ $ClientCert, $ClientCertPassword
+ )
+ }
+ catch [System.Security.Cryptography.CryptographicException] {
+ Write-Error -Message "Failed to read client certificate at '$ClientCert'" -Exception $_.Exception -Category SecurityError
+ return
+ }
+ }
+ $web_request.ClientCertificates = New-Object -TypeName "$crypto_ns.X509Certificate2Collection" -ArgumentList @(
+ $cert
+ )
+ }
+
+ if (-not $UseProxy) {
+ $proxy = $null
+ }
+ elseif ($ProxyUrl) {
+ $proxy = New-Object -TypeName System.Net.WebProxy -ArgumentList $ProxyUrl, $true
+ }
+ else {
+ $proxy = $web_request.Proxy
+ }
+
+ # $web_request.Proxy may return $null for a FTP web request. We only set the credentials if we have an actual
+ # proxy to work with, otherwise just ignore the credentials property.
+ if ($null -ne $proxy) {
+ if ($ProxyUseDefaultCredential) {
+ # Weird hack, $web_request.Proxy returns an IWebProxy object which only guarantees the Credentials
+ # property. We cannot set UseDefaultCredentials so we just set the Credentials to the
+ # DefaultCredentials in the CredentialCache which does the same thing.
+ $proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials
+ }
+ elseif ($ProxyUsername) {
+ $proxy.Credentials = New-Object -TypeName System.Net.NetworkCredential -ArgumentList @(
+ $ProxyUsername, $ProxyPassword
+ )
+ }
+ else {
+ $proxy.Credentials = $null
+ }
+ }
+
+ $web_request.Proxy = $proxy
+
+ # Some parameters only apply when dealing with a HttpWebRequest
+ if ($web_request -is [System.Net.HttpWebRequest]) {
+ if ($Headers) {
+ foreach ($header in $Headers.GetEnumerator()) {
+ switch ($header.Key) {
+ Accept { $web_request.Accept = $header.Value }
+ Connection { $web_request.Connection = $header.Value }
+ Content-Length { $web_request.ContentLength = $header.Value }
+ Content-Type { $web_request.ContentType = $header.Value }
+ Expect { $web_request.Expect = $header.Value }
+ Date { $web_request.Date = $header.Value }
+ Host { $web_request.Host = $header.Value }
+ If-Modified-Since { $web_request.IfModifiedSince = $header.Value }
+ Range { $web_request.AddRange($header.Value) }
+ Referer { $web_request.Referer = $header.Value }
+ Transfer-Encoding {
+ $web_request.SendChunked = $true
+ $web_request.TransferEncoding = $header.Value
+ }
+ User-Agent { continue }
+ default { $web_request.Headers.Add($header.Key, $header.Value) }
+ }
+ }
+ }
+
+ # For backwards compatibility we need to support setting the User-Agent if the header was set in the task.
+ # We just need to make sure that if an explicit http_agent module was set then that takes priority.
+ if ($Headers -and $Headers.ContainsKey("User-Agent")) {
+ if ($HttpAgent -eq $ansible_web_request_options.http_agent.default) {
+ $HttpAgent = $Headers['User-Agent']
+ }
+ elseif ($null -ne $Module) {
+ $Module.Warn("The 'User-Agent' header and the 'http_agent' was set, using the 'http_agent' for web request")
+ }
+ }
+ $web_request.UserAgent = $HttpAgent
+
+ switch ($FollowRedirects) {
+ none { $web_request.AllowAutoRedirect = $false }
+ safe {
+ if ($web_request.Method -in @("GET", "HEAD")) {
+ $web_request.AllowAutoRedirect = $true
+ }
+ else {
+ $web_request.AllowAutoRedirect = $false
+ }
+ }
+ all { $web_request.AllowAutoRedirect = $true }
+ }
+
+ if ($MaximumRedirection -eq 0) {
+ $web_request.AllowAutoRedirect = $false
+ }
+ else {
+ $web_request.MaximumAutomaticRedirections = $MaximumRedirection
+ }
+ }
+
+ return $web_request
+}
+
+Function Invoke-WithWebRequest {
+ <#
+ .SYNOPSIS
+ Invokes a ScriptBlock with the WebRequest.
+
+ .DESCRIPTION
+ Invokes the ScriptBlock and handle extra information like accessing the response stream, closing those streams
+ safely as well as setting common module return values.
+
+ .PARAMETER Module
+ The Ansible.Basic module to set the return values for. This will set the following return values;
+ elapsed - The total time, in seconds, that it took to send the web request and process the response
+ msg - The human readable description of the response status code
+ status_code - An int that is the response status code
+
+ .PARAMETER Request
+ The System.Net.WebRequest to call. This can either be manually crafted or created with Get-AnsibleWebRequest.
+
+ .PARAMETER Script
+ The ScriptBlock to invoke during the web request. This ScriptBlock should take in the params
+ Param ([System.Net.WebResponse]$Response, [System.IO.Stream]$Stream)
+
+ This scriptblock should manage the response based on what it need to do.
+
+ .PARAMETER Body
+ An optional Stream to send to the target during the request.
+
+ .PARAMETER IgnoreBadResponse
+ By default a WebException will be raised for a non 2xx status code and the Script will not be invoked. This
+ parameter can be set to process all responses regardless of the status code.
+
+ .EXAMPLE Basic module that downloads a file
+ $spec = @{
+ options = @{
+ path = @{ type = "path"; required = $true }
+ }
+ }
+ $module = Ansible.Basic.AnsibleModule]::Create($args, $spec, @(Get-AnsibleWebRequestSpec))
+
+ $web_request = Get-AnsibleWebRequest -Module $module
+
+ Invoke-WithWebRequest -Module $module -Request $web_request -Script {
+ Param ([System.Net.WebResponse]$Response, [System.IO.Stream]$Stream)
+
+ $fs = [System.IO.File]::Create($module.Params.path)
+ try {
+ $Stream.CopyTo($fs)
+ $fs.Flush()
+ } finally {
+ $fs.Dispose()
+ }
+ }
+ #>
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory = $true)]
+ [System.Object]
+ [ValidateScript({ $_.GetType().FullName -eq 'Ansible.Basic.AnsibleModule' })]
+ $Module,
+
+ [Parameter(Mandatory = $true)]
+ [System.Net.WebRequest]
+ $Request,
+
+ [Parameter(Mandatory = $true)]
+ [ScriptBlock]
+ $Script,
+
+ [AllowNull()]
+ [System.IO.Stream]
+ $Body,
+
+ [Switch]
+ $IgnoreBadResponse
+ )
+
+ $start = Get-Date
+ if ($null -ne $Body) {
+ $request_st = $Request.GetRequestStream()
+ try {
+ $Body.CopyTo($request_st)
+ $request_st.Flush()
+ }
+ finally {
+ $request_st.Close()
+ }
+ }
+
+ try {
+ try {
+ $web_response = $Request.GetResponse()
+ }
+ catch [System.Net.WebException] {
+ # A WebResponse with a status code not in the 200 range will raise a WebException. We check if the
+ # exception raised contains the actual response and continue on if IgnoreBadResponse is set. We also
+ # make sure we set the status_code return value on the Module object if possible
+
+ if ($_.Exception.PSObject.Properties.Name -match "Response") {
+ $web_response = $_.Exception.Response
+
+ if (-not $IgnoreBadResponse -or $null -eq $web_response) {
+ $Module.Result.msg = $_.Exception.StatusDescription
+ $Module.Result.status_code = $_.Exception.Response.StatusCode
+ throw $_
+ }
+ }
+ else {
+ throw $_
+ }
+ }
+
+ if ($Request.RequestUri.IsFile) {
+ # A FileWebResponse won't have these properties set
+ $Module.Result.msg = "OK"
+ $Module.Result.status_code = 200
+ }
+ else {
+ $Module.Result.msg = $web_response.StatusDescription
+ $Module.Result.status_code = $web_response.StatusCode
+ }
+
+ $response_stream = $web_response.GetResponseStream()
+ try {
+ # Invoke the ScriptBlock and pass in WebResponse and ResponseStream
+ &$Script -Response $web_response -Stream $response_stream
+ }
+ finally {
+ $response_stream.Dispose()
+ }
+ }
+ finally {
+ if ($web_response) {
+ $web_response.Close()
+ }
+ $Module.Result.elapsed = ((Get-date) - $start).TotalSeconds
+ }
+}
+
+Function Get-AnsibleWebRequestSpec {
+ <#
+ .SYNOPSIS
+ Used by modules to get the argument spec fragment for AnsibleModule.
+
+ .EXAMPLES
+ $spec = @{
+ options = @{}
+ }
+ $module = [Ansible.Basic.AnsibleModule]::Create($args, $spec, @(Get-AnsibleWebRequestSpec))
+ #>
+ @{ options = $ansible_web_request_options }
+}
+
+# See lib/ansible/plugins/doc_fragments/url_windows.py
+# Kept here for backwards compat as this variable was added in Ansible 2.9. Ultimately this util should be removed
+# once the deprecation period has been added.
+$ansible_web_request_options = @{
+ method = @{ type = "str" }
+ follow_redirects = @{ type = "str"; choices = @("all", "none", "safe"); default = "safe" }
+ headers = @{ type = "dict" }
+ http_agent = @{ type = "str"; default = "ansible-httpget" }
+ maximum_redirection = @{ type = "int"; default = 50 }
+ timeout = @{ type = "int"; default = 30 } # Was defaulted to 10 in win_get_url but 30 in win_uri so we use 30
+ validate_certs = @{ type = "bool"; default = $true }
+
+ # Credential options
+ client_cert = @{ type = "str" }
+ client_cert_password = @{ type = "str"; no_log = $true }
+ force_basic_auth = @{ type = "bool"; default = $false }
+ url_username = @{ type = "str" }
+ url_password = @{ type = "str"; no_log = $true }
+ use_default_credential = @{ type = "bool"; default = $false }
+
+ # Proxy options
+ use_proxy = @{ type = "bool"; default = $true }
+ proxy_url = @{ type = "str" }
+ proxy_username = @{ type = "str" }
+ proxy_password = @{ type = "str"; no_log = $true }
+ proxy_use_default_credential = @{ type = "bool"; default = $false }
+}
+
+$export_members = @{
+ Function = "Get-AnsibleWebRequest", "Get-AnsibleWebRequestSpec", "Invoke-WithWebRequest"
+ Variable = "ansible_web_request_options"
+}
+Export-ModuleMember @export_members
diff --git a/lib/ansible/module_utils/powershell/__init__.py b/lib/ansible/module_utils/powershell/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/ansible/module_utils/powershell/__init__.py