mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-24 22:00:22 -07:00
win_dsc - Add argument validation and other fixes (#53093)
* win_dsc - Add argument validation and other fixes * Fix doc issues
This commit is contained in:
parent
853a65eead
commit
6b294eab4d
27 changed files with 1585 additions and 990 deletions
|
@ -4,272 +4,400 @@
|
|||
# Copyright: (c) 2017, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#Requires -Version 5
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
Function ConvertTo-ArgSpecType {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Converts the DSC parameter type to the arg spec type required for Ansible.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][String]$CimType
|
||||
)
|
||||
|
||||
$params = Parse-Args $args -supports_check_mode $true
|
||||
$result = @{
|
||||
changed = $false
|
||||
$arg_type = switch($CimType) {
|
||||
Boolean { "bool" }
|
||||
Char16 { [Func[[Object], [Char]]]{ [System.Char]::Parse($args[0].ToString()) } }
|
||||
DateTime { [Func[[Object], [DateTime]]]{
|
||||
# o == ISO 8601 format
|
||||
[System.DateTime]::ParseExact($args[0].ToString(), "o", [CultureInfo]::InvariantCulture,
|
||||
[System.Globalization.DateTimeStyles]::None)
|
||||
}}
|
||||
Instance { "dict" }
|
||||
Real32 { "float" }
|
||||
Real64 { [Func[[Object], [Double]]]{ [System.Double]::Parse($args[0].ToString()) } }
|
||||
Reference { "dict" }
|
||||
SInt16 { [Func[[Object], [Int16]]]{ [System.Int16]::Parse($args[0].ToString()) } }
|
||||
SInt32 { "int" }
|
||||
SInt64 { [Func[[Object], [Int64]]]{ [System.Int64]::Parse($args[0].ToString()) } }
|
||||
SInt8 { [Func[[Object], [SByte]]]{ [System.SByte]::Parse($args[0].ToString()) } }
|
||||
String { "str" }
|
||||
UInt16 { [Func[[Object], [UInt16]]]{ [System.UInt16]::Parse($args[0].ToString()) } }
|
||||
UInt32 { [Func[[Object], [UInt32]]]{ [System.UInt32]::Parse($args[0].ToString()) } }
|
||||
UInt64 { [Func[[Object], [UInt64]]]{ [System.UInt64]::Parse($args[0].ToString()) } }
|
||||
UInt8 { [Func[[Object], [Byte]]]{ [System.Byte]::Parse($args[0].ToString()) } }
|
||||
Unknown { "raw" }
|
||||
default { "raw" }
|
||||
}
|
||||
return $arg_type
|
||||
}
|
||||
|
||||
Function Cast-ToCimInstance($name, $value, $className)
|
||||
{
|
||||
# this converts a hashtable to a CimInstance
|
||||
Function Get-DscCimClassProperties {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get's a list of CimProperties of a CIM Class. It filters out any magic or
|
||||
read only properties that we don't need to know about.
|
||||
#>
|
||||
param([Parameter(Mandatory=$true)][String]$ClassName)
|
||||
|
||||
$valueType = $value.GetType()
|
||||
if ($valueType -ne [hashtable])
|
||||
{
|
||||
Fail-Json -obj $result -message "CimInstance value for property $name must be a hashtable, was $($valueType.FullName)"
|
||||
$resource = Get-CimClass -ClassName $ClassName -Namespace root\Microsoft\Windows\DesiredStateConfiguration
|
||||
|
||||
# Filter out any magic properties that are used internally on an OMI_BaseResource
|
||||
# https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/DscSupport/CimDSCParser.cs#L1203
|
||||
$magic_properties = @("ResourceId", "SourceInfo", "ModuleName", "ModuleVersion", "ConfigurationName")
|
||||
$properties = $resource.CimClassProperties | Where-Object {
|
||||
|
||||
($resource.CimSuperClassName -ne "OMI_BaseResource" -or $_.Name -notin $magic_properties) -and
|
||||
-not $_.Flags.HasFlag([Microsoft.Management.Infrastructure.CimFlags]::ReadOnly)
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$cim = New-CimInstance -ClassName $className -Property $value -ClientOnly
|
||||
}
|
||||
catch
|
||||
{
|
||||
Fail-Json -obj $result -message "Failed to convert hashtable to CimInstance of $($className): $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
return ,$cim
|
||||
return ,$properties
|
||||
}
|
||||
|
||||
Function Cast-Value($value, $type, $typeString, $name)
|
||||
{
|
||||
if ($type -eq [CimInstance])
|
||||
{
|
||||
$newValue = Cast-ToCimInstance -name $name -value $value -className $typeString
|
||||
Function Add-PropertyOption {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Adds the spec for the property type to the existing module specification.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][Hashtable]$Spec,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[Microsoft.Management.Infrastructure.CimPropertyDeclaration]$Property
|
||||
)
|
||||
|
||||
$option = @{
|
||||
required = $false
|
||||
}
|
||||
ElseIf ($type -eq [CimInstance[]])
|
||||
{
|
||||
if ($value -isnot [array])
|
||||
{
|
||||
$value = @($value)
|
||||
}
|
||||
[CimInstance[]]$newValue = @()
|
||||
$baseTypeString = $typeString.Substring(0, $typeString.Length - 2)
|
||||
foreach ($cim in $value)
|
||||
{
|
||||
$newValue += Cast-ToCimInstance -name $name -value $cim -className $baseTypeString
|
||||
}
|
||||
$property_name = $Property.Name
|
||||
$property_type = $Property.CimType.ToString()
|
||||
|
||||
if ($Property.Flags.HasFlag([Microsoft.Management.Infrastructure.CimFlags]::Key) -or
|
||||
$Property.Flags.HasFlag([Microsoft.Management.Infrastructure.CimFlags]::Required)) {
|
||||
$option.required = $true
|
||||
}
|
||||
Else
|
||||
{
|
||||
$originalType = $value.GetType()
|
||||
if ($originalType -eq $type)
|
||||
{
|
||||
$newValue = $value
|
||||
|
||||
if ($null -ne $Property.Qualifiers['Values']) {
|
||||
$option.choices = [System.Collections.Generic.List`1[Object]]$Property.Qualifiers['Values'].Value
|
||||
}
|
||||
|
||||
if ($property_name -eq "Name") {
|
||||
# For backwards compatibility we support specifying the Name DSC property as item_name
|
||||
$option.aliases = @("item_name")
|
||||
} elseif ($property_name -ceq "key") {
|
||||
# There seems to be a bug in the CIM property parsing when the property name is 'Key'. The CIM instance will
|
||||
# think the name is 'key' when the MOF actually defines it as 'Key'. We set the proper casing so the module arg
|
||||
# validator won't fire a case sensitive warning
|
||||
$property_name = "Key"
|
||||
}
|
||||
|
||||
if ($Property.ReferenceClassName -eq "MSFT_Credential") {
|
||||
# Special handling for the MSFT_Credential type (PSCredential), we handle this with having 2 options that
|
||||
# have the suffix _username and _password.
|
||||
$option_spec_pass = @{
|
||||
type = "str"
|
||||
required = $option.required
|
||||
no_log = $true
|
||||
}
|
||||
Else
|
||||
{
|
||||
$newValue = $value -as $type
|
||||
if ($newValue -eq $null)
|
||||
{
|
||||
Add-Warning -obj $result -message "failed to cast property $name from '$value' of type $($originalType.FullName) to type $($type.FullName), the DSC engine may ignore this property with an invalid cast"
|
||||
$newValue = $value
|
||||
$Spec.options."$($property_name)_password" = $option_spec_pass
|
||||
$Spec.required_together.Add(@("$($property_name)_username", "$($property_name)_password")) > $null
|
||||
|
||||
$property_name = "$($property_name)_username"
|
||||
$option.type = "str"
|
||||
} elseif ($Property.ReferenceClassName -eq "MSFT_KeyValuePair") {
|
||||
$option.type = "dict"
|
||||
} elseif ($property_type.EndsWith("Array")) {
|
||||
$option.type = "list"
|
||||
$option.elements = ConvertTo-ArgSpecType -CimType $property_type.Substring(0, $property_type.Length - 5)
|
||||
} else {
|
||||
$option.type = ConvertTo-ArgSpecType -CimType $property_type
|
||||
}
|
||||
|
||||
if (($option.type -eq "dict" -or ($option.type -eq "list" -and $option.elements -eq "dict")) -and
|
||||
$Property.ReferenceClassName -ne "MSFT_KeyValuePair") {
|
||||
# Get the sub spec if the type is a Instance (CimInstance/dict)
|
||||
$sub_option_spec = Get-OptionSpec -ClassName $Property.ReferenceClassName
|
||||
$option += $sub_option_spec
|
||||
}
|
||||
|
||||
$Spec.options.$property_name = $option
|
||||
}
|
||||
|
||||
Function Get-OptionSpec {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Generates the specifiec used in AnsibleModule for a CIM MOF resource name.
|
||||
|
||||
.NOTES
|
||||
This won't be able to retrieve the default values for an option as that is not defined in the MOF for a resource.
|
||||
Default values are still preserved in the DSC engine if we don't pass in the property at all, we just can't report
|
||||
on what they are automatically.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][String]$ClassName
|
||||
)
|
||||
|
||||
$spec = @{
|
||||
options = @{}
|
||||
required_together = [System.Collections.ArrayList]@()
|
||||
}
|
||||
$properties = Get-DscCimClassProperties -ClassName $ClassName
|
||||
foreach ($property in $properties) {
|
||||
Add-PropertyOption -Spec $spec -Property $property
|
||||
}
|
||||
|
||||
return $spec
|
||||
}
|
||||
|
||||
Function ConvertTo-CimInstance {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Converts a dict to a CimInstance of the specified Class. Also provides a
|
||||
better error message if this fails that contains the option name that failed.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][String]$Name,
|
||||
[Parameter(Mandatory=$true)][String]$ClassName,
|
||||
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Value,
|
||||
[Parameter(Mandatory=$true)][Ansible.Basic.AnsibleModule]$Module,
|
||||
[Switch]$Recurse
|
||||
)
|
||||
|
||||
$properties = @{}
|
||||
foreach ($value_info in $Value.GetEnumerator()) {
|
||||
# Need to remove all null values from existing dict so the conversion works
|
||||
if ($null -eq $value_info.Value) {
|
||||
continue
|
||||
}
|
||||
$properties.($value_info.Key) = $value_info.Value
|
||||
}
|
||||
|
||||
if ($Recurse) {
|
||||
# We want to validate and convert and values to what's required by DSC
|
||||
$properties = ConvertTo-DscProperty -ClassName $ClassName -Params $properties -Module $Module
|
||||
}
|
||||
|
||||
try {
|
||||
return (New-CimInstance -ClassName $ClassName -Property $properties -ClientOnly)
|
||||
} catch {
|
||||
# New-CimInstance raises a poor error message, make sure we mention what option it is for
|
||||
$Module.FailJson("Failed to cast dict value for option '$Name' to a CimInstance: $($_.Exception.Message)", $_)
|
||||
}
|
||||
}
|
||||
|
||||
Function ConvertTo-DscProperty {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Converts the input module parameters that have been validated and casted
|
||||
into the types expected by the DSC engine. This is mostly done to deal with
|
||||
types like PSCredential and Dictionaries.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][String]$ClassName,
|
||||
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Params,
|
||||
[Parameter(Mandatory=$true)][Ansible.Basic.AnsibleModule]$Module
|
||||
)
|
||||
$properties = Get-DscCimClassProperties -ClassName $ClassName
|
||||
|
||||
$dsc_properties = @{}
|
||||
foreach ($property in $properties) {
|
||||
$property_name = $property.Name
|
||||
$property_type = $property.CimType.ToString()
|
||||
|
||||
if ($property.ReferenceClassName -eq "MSFT_Credential") {
|
||||
$username = $Params."$($property_name)_username"
|
||||
$password = $Params."$($property_name)_password"
|
||||
|
||||
# No user set == No option set in playbook, skip this property
|
||||
if ($null -eq $username) {
|
||||
continue
|
||||
}
|
||||
$sec_password = ConvertTo-SecureString -String $password -AsPlainText -Force
|
||||
$value = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username, $sec_password
|
||||
} else {
|
||||
$value = $Params.$property_name
|
||||
|
||||
# The actual value wasn't set, skip adding this property
|
||||
if ($null -eq $value) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ($property.ReferenceClassName -eq "MSFT_KeyValuePair") {
|
||||
$key_value_pairs = [System.Collections.Generic.List`1[CimInstance]]@()
|
||||
foreach ($value_info in $value.GetEnumerator()) {
|
||||
$kvp = @{Key = $value_info.Key; Value = $value_info.Value.ToString()}
|
||||
$cim_instance = ConvertTo-CimInstance -Name $property_name -ClassName MSFT_KeyValuePair `
|
||||
-Value $kvp -Module $Module
|
||||
$key_value_pairs.Add($cim_instance) > $null
|
||||
}
|
||||
$value = $key_value_pairs.ToArray()
|
||||
} elseif ($null -ne $property.ReferenceClassName) {
|
||||
# Convert the dict to a CimInstance (or list of CimInstances)
|
||||
$convert_args = @{
|
||||
ClassName = $property.ReferenceClassName
|
||||
Module = $Module
|
||||
Name = $property_name
|
||||
Recurse = $true
|
||||
}
|
||||
if ($property_type.EndsWith("Array")) {
|
||||
$value = [System.Collections.Generic.List`1[CimInstance]]@()
|
||||
foreach ($raw in $Params.$property_name.GetEnumerator()) {
|
||||
$cim_instance = ConvertTo-CimInstance -Value $raw @convert_args
|
||||
$value.Add($cim_instance) > $null
|
||||
}
|
||||
$value = $value.ToArray() # Need to make sure we are dealing with an Array not a List
|
||||
} else {
|
||||
$value = ConvertTo-CimInstance -Value $value @convert_args
|
||||
}
|
||||
}
|
||||
}
|
||||
$dsc_properties.$property_name = $value
|
||||
}
|
||||
|
||||
return ,$newValue
|
||||
return $dsc_properties
|
||||
}
|
||||
|
||||
Function Parse-DscProperty($name, $value, $resourceProp)
|
||||
{
|
||||
$propertyTypeString = $resourceProp.PropertyType
|
||||
if ($propertyTypeString.StartsWith("["))
|
||||
{
|
||||
$propertyTypeString = $propertyTypeString.Substring(1, $propertyTypeString.Length - 2)
|
||||
}
|
||||
$propertyType = $propertyTypeString -as [type]
|
||||
Function Invoke-DscMethod {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Invokes the DSC Resource Method specified in another PS pipeline. This is
|
||||
done so we can retrieve the Verbose stream and return it back to the user
|
||||
for futher debugging.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][Ansible.Basic.AnsibleModule]$Module,
|
||||
[Parameter(Mandatory=$true)][String]$Method,
|
||||
[Parameter(Mandatory=$true)][Hashtable]$Arguments
|
||||
)
|
||||
|
||||
# CimInstance and CimInstance[] are reperesented as the actual Cim
|
||||
# ClassName and the above returns a $null. We need to manually set the
|
||||
# type in these cases
|
||||
if ($propertyType -eq $null)
|
||||
{
|
||||
if ($propertyTypeString.EndsWith("[]"))
|
||||
{
|
||||
$propertyType = [CimInstance[]]
|
||||
}
|
||||
Else
|
||||
{
|
||||
$propertyType = [CimInstance]
|
||||
# Invoke the DSC resource in a separate runspace so we can capture the Verbose output
|
||||
$ps = [PowerShell]::Create()
|
||||
$ps.AddCommand("Invoke-DscResource").AddParameter("Method", $Method) > $null
|
||||
$ps.AddParameters($Arguments) > $null
|
||||
|
||||
$result = $ps.Invoke()
|
||||
|
||||
# Pass the warnings through to the AnsibleModule return result
|
||||
foreach ($warning in $ps.Streams.Warning) {
|
||||
$Module.Warn($warning.Message)
|
||||
}
|
||||
|
||||
# If running at a high enough verbosity, add the verbose output to the AnsibleModule return result
|
||||
if ($Module.Verbosity -ge 3) {
|
||||
$verbose_logs = [System.Collections.Generic.List`1[String]]@()
|
||||
foreach ($verbosity in $ps.Streams.Verbose) {
|
||||
$verbose_logs.Add($verbosity.Message) > $null
|
||||
}
|
||||
$Module.Result."verbose_$($Method.ToLower())" = $verbose_logs
|
||||
}
|
||||
|
||||
if ($propertyType.IsArray)
|
||||
{
|
||||
# convert the value to a list for later conversion
|
||||
if ($value -is [string])
|
||||
{
|
||||
$value = $value.Split(",").Trim()
|
||||
}
|
||||
ElseIf ($value -isnot [array])
|
||||
{
|
||||
$value = @($value)
|
||||
}
|
||||
if ($ps.HadErrors) {
|
||||
# Cannot pass in the ErrorRecord as it's a RemotingErrorRecord and doesn't contain the ScriptStackTrace
|
||||
# or other info that would be useful
|
||||
$Module.FailJson("Failed to invoke DSC $Method method: $($ps.Streams.Error[0].Exception.Message)")
|
||||
}
|
||||
$newValue = Cast-Value -value $value -type $propertyType -typeString $propertyTypeString -name $name
|
||||
|
||||
return ,$newValue
|
||||
return $result
|
||||
}
|
||||
|
||||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
|
||||
$resourcename = Get-AnsibleParam -obj $params -name "resource_name" -type "str" -failifempty $true
|
||||
$module_version = Get-AnsibleParam -obj $params -name "module_version" -type "str" -default "latest"
|
||||
|
||||
#From Ansible 2.3 onwards, params is now a Hash Array
|
||||
$Attributes = @{}
|
||||
foreach ($param in $params.GetEnumerator())
|
||||
{
|
||||
if ($param.Name -notin @("resource_name", "module_version") -and $param.Name -notlike "_ansible_*")
|
||||
{
|
||||
$Attributes[$param.Name] = $param.Value
|
||||
# win_dsc is unique in that is builds the arg spec based on DSC Resource input. To get this info
|
||||
# we need to read the resource_name and module_version value which is done outside of Ansible.Basic
|
||||
if ($args.Length -gt 0) {
|
||||
$params = Get-Content -Path $args[0] | ConvertFrom-Json
|
||||
} else {
|
||||
$params = $complex_args
|
||||
}
|
||||
if (-not $params.ContainsKey("resource_name")) {
|
||||
$res = @{
|
||||
msg = "missing required argument: resource_name"
|
||||
failed = $true
|
||||
}
|
||||
Write-Output -InputObject (ConvertTo-Json -Compress -InputObject $res)
|
||||
exit 1
|
||||
}
|
||||
$resource_name = $params.resource_name
|
||||
|
||||
if ($params.ContainsKey("module_version")) {
|
||||
$module_version = $params.module_version
|
||||
} else {
|
||||
$module_version = "latest"
|
||||
}
|
||||
|
||||
if ($Attributes.Count -eq 0)
|
||||
{
|
||||
Fail-Json -obj $result -message "No attributes specified"
|
||||
$module_versions = (Get-DscResource -Name $resource_name -ErrorAction SilentlyContinue | Sort-Object -Property Version)
|
||||
$resource = $null
|
||||
if ($module_version -eq "latest" -and $null -ne $module_versions) {
|
||||
$resource = $module_versions[-1]
|
||||
} elseif ($module_version -ne "latest") {
|
||||
$resource = $module_versions | Where-Object { $_.Version -eq $module_version }
|
||||
}
|
||||
|
||||
#Always return some basic info
|
||||
$result["reboot_required"] = $false
|
||||
|
||||
$Config = @{
|
||||
Name = ($resourcename)
|
||||
Property = @{}
|
||||
}
|
||||
|
||||
#Get the latest version of the module
|
||||
if ($module_version -eq "latest")
|
||||
{
|
||||
$Resource = Get-DscResource -Name $resourcename -ErrorAction SilentlyContinue | sort Version | select -Last 1
|
||||
}
|
||||
else
|
||||
{
|
||||
$Resource = Get-DscResource -Name $resourcename -ErrorAction SilentlyContinue | where {$_.Version -eq $module_version}
|
||||
}
|
||||
|
||||
if (!$Resource)
|
||||
{
|
||||
if ($module_version -eq "latest")
|
||||
{
|
||||
Fail-Json -obj $result -message "Resource $resourcename not found"
|
||||
if (-not $resource) {
|
||||
if ($module_version -eq "latest") {
|
||||
$msg = "Resource '$resource_name' not found."
|
||||
} else {
|
||||
$msg = "Resource '$resource_name' with version '$module_version' not found."
|
||||
$msg += " Versions installed: '$($module_versions.Version -join "', '")'."
|
||||
}
|
||||
else
|
||||
{
|
||||
Fail-Json -obj $result -message "Resource $resourcename with version $module_version not found"
|
||||
}
|
||||
|
||||
|
||||
Write-Output -InputObject (ConvertTo-Json -Compress -InputObject @{ failed = $true; msg = $msg })
|
||||
exit 1
|
||||
}
|
||||
|
||||
#Get the Module that provides the resource. Will be used as
|
||||
#mandatory argument for Invoke-DscResource
|
||||
$Module = @{
|
||||
ModuleName = $Resource.ModuleName
|
||||
ModuleVersion = $Resource.Version
|
||||
# Build the base args for the DSC Invocation based on the resource selected
|
||||
$dsc_args = @{
|
||||
Name = $resource.Name
|
||||
}
|
||||
|
||||
# Binary resources are not working very well with that approach - need to guesstimate module name/version
|
||||
if ( -not ($Module.ModuleName -or $Module.ModuleVersion)) {
|
||||
$Module = 'PSDesiredStateConfiguration'
|
||||
$module_version = $null
|
||||
if ($resource.Module) {
|
||||
$dsc_args.ModuleName = @{
|
||||
ModuleName = $resource.Module.Name
|
||||
ModuleVersion = $resource.Module.Version
|
||||
}
|
||||
$module_version = $resource.Module.Version.ToString()
|
||||
} else {
|
||||
$dsc_args.ModuleName = "PSDesiredStateConfiguration"
|
||||
}
|
||||
|
||||
#grab the module version if we can
|
||||
# To ensure the class registered with CIM is the one based on our version, we want to run the Get method so the DSC
|
||||
# engine updates the metadata propery. We don't care about any errors here
|
||||
try {
|
||||
if ($Resource.Module.Version)
|
||||
{
|
||||
$result["module_version"] = $Resource.Module.Version.ToString()
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
Invoke-DscResource -Method Get -Property @{Fake="Fake"} @dsc_args > $null
|
||||
} catch {}
|
||||
|
||||
#Convert params to correct datatype and inject
|
||||
foreach ($attribute in $Attributes.GetEnumerator())
|
||||
{
|
||||
$key = $attribute.Name.Replace("item_name", "name")
|
||||
$value = $attribute.Value
|
||||
$prop = $resource.Properties | Where-Object {$_.Name -eq $key}
|
||||
if (!$prop)
|
||||
{
|
||||
#If its a credential specified as "credential", Ansible will support credential_username and credential_password. Need to check for that
|
||||
$prop = $resource.Properties | Where-Object {$_.Name -eq $key.Replace("_username","")}
|
||||
if ($prop)
|
||||
{
|
||||
#We need to construct a cred object. At this point keyvalue is the username, so grab the password
|
||||
$PropUserNameValue = $value
|
||||
$PropPassword = $key.Replace("_username","_password")
|
||||
$PropPasswordValue = $Attributes.$PropPassword
|
||||
# Dynamically build the option spec based on the resource_name specified and create the module object
|
||||
$spec = Get-OptionSpec -ClassName $resource.ResourceType
|
||||
$spec.supports_check_mode = $true
|
||||
$spec.options.module_version = @{ type = "str"; default = "latest" }
|
||||
$spec.options.resource_name = @{ type = "str"; required = $true }
|
||||
|
||||
$KeyValue = New-Object System.Management.Automation.PSCredential ($PropUserNameValue, ($PropPasswordValue | ConvertTo-SecureString -AsPlainText -Force))
|
||||
$config.Property.Add($key.Replace("_username",""),$KeyValue)
|
||||
}
|
||||
ElseIf ($key.Contains("_password"))
|
||||
{
|
||||
#Do nothing. We suck in the password in the handler for _username, so we can just skip it.
|
||||
}
|
||||
Else
|
||||
{
|
||||
Fail-Json -obj $result -message "Property $key in resource $resourcename is not a valid property"
|
||||
}
|
||||
}
|
||||
Else
|
||||
{
|
||||
if ($value -eq $null)
|
||||
{
|
||||
$keyValue = $null
|
||||
}
|
||||
Else
|
||||
{
|
||||
$keyValue = Parse-DscProperty -name $key -value $value -resourceProp $prop
|
||||
}
|
||||
|
||||
$config.Property.Add($key, $keyValue)
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
$module.Result.reboot_required = $false
|
||||
$module.Result.module_version = $module_version
|
||||
|
||||
# Build the DSC invocation arguments and invoke the resource
|
||||
$dsc_args.Property = ConvertTo-DscProperty -ClassName $resource.ResourceType -Module $module -Params $Module.Params
|
||||
$dsc_args.Verbose = $true
|
||||
|
||||
$test_result = Invoke-DscMethod -Module $module -Method Test -Arguments $dsc_args
|
||||
if ($test_result.InDesiredState -ne $true) {
|
||||
if (-not $module.CheckMode) {
|
||||
$result = Invoke-DscMethod -Module $module -Method Set -Arguments $dsc_args
|
||||
$module.Result.reboot_required = $result.RebootRequired
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
#Defined variables in strictmode
|
||||
$TestError, $TestError = $null
|
||||
$TestResult = Invoke-DscResource @Config -Method Test -ModuleName $Module -ErrorVariable TestError -ErrorAction SilentlyContinue -WarningVariable TestWarn
|
||||
foreach ($warning in $TestWarn) {
|
||||
Add-Warning -obj $result -message $warning.Message
|
||||
}
|
||||
$module.ExitJson()
|
||||
|
||||
if ($TestError)
|
||||
{
|
||||
throw ($TestError[0].Exception.Message)
|
||||
}
|
||||
ElseIf (($TestResult.InDesiredState) -ne $true)
|
||||
{
|
||||
if ($check_mode -eq $False)
|
||||
{
|
||||
$SetResult = Invoke-DscResource -Method Set @Config -ModuleName $Module -ErrorVariable SetError -ErrorAction SilentlyContinue -WarningVariable SetWarn
|
||||
foreach ($warning in $SetWarn) {
|
||||
Add-Warning -obj $result -message $warning.Message
|
||||
}
|
||||
if ($SetError -and ($SetResult -eq $null))
|
||||
{
|
||||
#If SetError was filled, throw to exit out of the try/catch loop
|
||||
throw $SetError
|
||||
}
|
||||
$result["reboot_required"] = $SetResult.RebootRequired
|
||||
}
|
||||
$result["changed"] = $true
|
||||
if ($SetError)
|
||||
{
|
||||
throw ($SetError[0].Exception.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
Catch
|
||||
{
|
||||
Fail-Json -obj $result -message $_[0].Exception.Message
|
||||
}
|
||||
|
||||
Exit-Json -obj $result
|
||||
|
|
|
@ -54,6 +54,11 @@ options:
|
|||
provided but a comma separated string also work. Use a list where
|
||||
possible as no escaping is required and it works with more complex types
|
||||
list C(CimInstance[]).
|
||||
- If the type of the DSC resource option is a C(DateTime), use a string in
|
||||
the form of an ISO 8901 string.
|
||||
- Since Ansible 2.8, Ansible will now validate the input fields against the
|
||||
DSC resource definition automatically. Older versions will silently
|
||||
ignore invalid fields.
|
||||
type: str
|
||||
required: true
|
||||
notes:
|
||||
|
@ -65,6 +70,9 @@ notes:
|
|||
- The DSC engine run's each task as the SYSTEM account, any resources that need
|
||||
to be accessed with a different account need to have C(PsDscRunAsCredential)
|
||||
set.
|
||||
- To see the valid options for a DSC resource, run the module with C(-vvv) to
|
||||
show the possible module invocation. Default values are not shown in this
|
||||
output but are applied within the DSC engine.
|
||||
author:
|
||||
- Trond Hindenes (@trondhindenes)
|
||||
'''
|
||||
|
@ -103,6 +111,11 @@ EXAMPLES = r'''
|
|||
Ensure: Present
|
||||
Type: Directory
|
||||
|
||||
- name: Call DSC resource with DateTime option
|
||||
win_dsc:
|
||||
resource_name: DateTimeResource
|
||||
DateTimeOption: '2019-02-22T13:57:31.2311892+00:00'
|
||||
|
||||
# more complex example using custom DSC resource and dict values
|
||||
- name: Setup the xWebAdministration module
|
||||
win_psmodule:
|
||||
|
@ -137,7 +150,7 @@ EXAMPLES = r'''
|
|||
RETURN = r'''
|
||||
module_version:
|
||||
description: The version of the dsc resource/module used.
|
||||
returned: success
|
||||
returned: always
|
||||
type: str
|
||||
sample: "1.0.1"
|
||||
reboot_required:
|
||||
|
@ -146,9 +159,24 @@ reboot_required:
|
|||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
message:
|
||||
description: Any error message from invoking the DSC resource.
|
||||
returned: error
|
||||
type: str
|
||||
sample: Multiple DSC modules found with resource name xyz
|
||||
verbose_test:
|
||||
description: The verbose output as a list from executing the DSC test
|
||||
method.
|
||||
returned: Ansible verbosity is -vvv or greater
|
||||
type: list
|
||||
sample: [
|
||||
"Perform operation 'Invoke CimMethod' with the following parameters, ",
|
||||
"[SERVER]: LCM: [Start Test ] [[File]DirectResourceAccess]",
|
||||
"Operation 'Invoke CimMethod' complete."
|
||||
]
|
||||
verbose_set:
|
||||
description: The verbose output as a list from executing the DSC Set
|
||||
method.
|
||||
returned: Ansible verbosity is -vvv or greater and a change occurred
|
||||
type: list
|
||||
sample: [
|
||||
"Perform operation 'Invoke CimMethod' with the following parameters, ",
|
||||
"[SERVER]: LCM: [Start Set ] [[File]DirectResourceAccess]",
|
||||
"Operation 'Invoke CimMethod' complete."
|
||||
]
|
||||
'''
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue