win_dsc: improved parameter handling (#31556)

* win_dsc: improved parameter handling

* removed uneeded try/catch leftover from testing

* removed undeed return values

* added custom DSC to fully test out casting

* fix up codestyle issues

* using new Requires ps version check

* fixed up error message check on earlier ps version
This commit is contained in:
Jordan Borean 2017-11-06 09:44:04 +10:00 committed by GitHub
commit 978a979566
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1127 additions and 313 deletions

View file

@ -1,75 +1,169 @@
#!powershell
# (c) 2015, Trond Hindenes <trond@hindenes.com>, and others
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# WANT_JSON
# POWERSHELL_COMMON
# (c) 2015, Trond Hindenes <trond@hindenes.com>, and others
# Copyright (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Temporary fix
#Set-StrictMode -Off
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Version 5
$ErrorActionPreference = "Stop"
$params = Parse-Args $args -supports_check_mode $true
$result = @{
changed = $false
}
#Check that we're on at least Powershell version 5
if ($PSVersionTable.PSVersion.Major -lt 5)
Function ConvertTo-HashtableFromPsCustomObject($psObject)
{
Fail-Json -obj $Result -message "This module only runs on Powershell version 5 or higher"
$hashtable = @{}
$psObject | Get-Member -MemberType *Property | ForEach-Object {
$value = $psObject.($_.Name)
if ($value -is [PSObject])
{
$value = ConvertTo-HashtableFromPsCustomObject -myPsObject $value
}
$hashtable.($_.Name) = $value
}
return ,$hashtable
}
Function Cast-ToCimInstance($name, $value, $className)
{
# this converts a hashtable to a CimInstance
if ($value -is [PSObject])
{
# convert to hashtable
$value = ConvertTo-HashtableFromPsCustomObject -psObject $value
}
$valueType = $value.GetType()
if ($valueType -ne [hashtable])
{
Fail-Json -obj $result -message "CimInstance value for property $name must be a hashtable, was $($valueType.FullName)"
}
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
}
Function Cast-Value($value, $type, $typeString, $name)
{
if ($type -eq [CimInstance])
{
$newValue = Cast-ToCimInstance -name $name -value $value -className $typeString
}
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
}
}
Else
{
$originalType = $value.GetType()
if ($originalType -eq $type)
{
$newValue = $value
}
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
}
}
}
return ,$newValue
}
Function Parse-DscProperty($name, $value, $resourceProp)
{
$propertyTypeString = $resourceProp.PropertyType
if ($propertyTypeString.StartsWith("["))
{
$propertyTypeString = $propertyTypeString.Substring(1, $propertyTypeString.Length - 2)
}
$propertyType = $propertyTypeString -as [type]
# 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]
}
}
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)
}
}
$newValue = Cast-Value -value $value -type $propertyType -typeString $propertyTypeString -name $name
return ,$newValue
}
$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 -resultobj $result
$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 = $params.GetEnumerator() |
Where-Object {
$_.key -ne "resource_name" -and
$_.key -ne "module_version" -and
$_.key -notlike "_ansible_*"
$Attributes = @{}
foreach ($param in $params.GetEnumerator())
{
if ($param.Name -notin @("resource_name", "module_version") -and $param.Name -notlike "_ansible_*")
{
$Attributes[$param.Name] = $param.Value
}
}
if (!($Attributes))
if ($Attributes.Count -eq 0)
{
Fail-Json -obj $result -message "No attributes specified"
}
#Always return some basic info
$result["resource_name"] = $resourcename
$result["attributes"] = $Attributes
$result["reboot_required"] = $null
# Build Attributes Hashtable for DSC Resource Propertys
$Attrib = @{}
foreach ($key in $Attributes)
{
$result[$key.name] = $key.value
$Attrib.Add($Key.Key,$Key.Value)
}
$result["dsc_attributes"] = $attrib
$result["reboot_required"] = $false
$Config = @{
Name = ($resourcename)
Property = @{
}
}
Property = @{}
}
#Get the latest version of the module
if ($module_version -eq "latest")
@ -115,24 +209,24 @@ try {
}
catch {}
#Convert params to correct datatype and inject
$attrib.Keys | foreach-object {
$Key = $_.replace("item_name", "name")
$prop = $resource.Properties | where {$_.Name -eq $key}
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 {$_.Name -eq $key.Replace("_username","")}
$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 = $attrib.Item($_)
$PropUserNameValue = $value
$PropPassword = $key.Replace("_username","_password")
$PropPasswordValue = $attrib.$PropPassword
$PropPasswordValue = $Attributes.$PropPassword
$cred = New-Object System.Management.Automation.PSCredential ($PropUserNameValue, ($PropPasswordValue | ConvertTo-SecureString -AsPlainText -Force))
[System.Management.Automation.PSCredential]$KeyValue = $cred
$KeyValue = New-Object System.Management.Automation.PSCredential ($PropUserNameValue, ($PropPasswordValue | ConvertTo-SecureString -AsPlainText -Force))
$config.Property.Add($key.Replace("_username",""),$KeyValue)
}
ElseIf ($key.Contains("_password"))
@ -143,91 +237,21 @@ $attrib.Keys | foreach-object {
{
Fail-Json -obj $result -message "Property $key in resource $resourcename is not a valid property"
}
}
ElseIf ($prop.PropertyType -eq "[string]")
Else
{
[String]$KeyValue = $attrib.Item($_)
$config.Property.Add($key,$KeyValue)
}
ElseIf ($prop.PropertyType -eq "[string[]]")
{
#KeyValue is an array of strings
[String]$TempKeyValue = $attrib.Item($_)
[String[]]$KeyValue = $TempKeyValue.Split(",").Trim()
$config.Property.Add($key,$KeyValue)
}
ElseIf ($prop.PropertyType -eq "[UInt32[]]")
{
#KeyValue is an array of integers
[String]$TempKeyValue = $attrib.Item($_)
[UInt32[]]$KeyValue = $attrib.Item($_.split(",").Trim())
$config.Property.Add($key,$KeyValue)
}
ElseIf ($prop.PropertyType -eq "[bool]")
{
if ($attrib.Item($_) -like "true")
if ($value -eq $null)
{
[bool]$KeyValue = $true
$keyValue = $null
}
ElseIf ($attrib.Item($_) -like "false")
Else
{
[bool]$KeyValue = $false
$keyValue = Parse-DscProperty -name $key -value $value -resourceProp $prop
}
$config.Property.Add($key,$KeyValue)
$config.Property.Add($key, $keyValue)
}
ElseIf ($prop.PropertyType -eq "[int]")
{
[int]$KeyValue = $attrib.Item($_)
$config.Property.Add($key,$KeyValue)
}
ElseIf ($prop.PropertyType -eq "[CimInstance[]]")
{
#KeyValue is an array of CimInstance
[CimInstance[]]$KeyVal = @()
[String]$TempKeyValue = $attrib.Item($_)
#Need to split on the string }, because some property values have commas in them
[String[]]$KeyValueStr = $TempKeyValue -split("},")
#Go through each string of properties and create a hash of them
foreach($str in $KeyValueStr)
{
[string[]]$properties = $str.Split("{")[1].Replace("}","").Trim().Split([environment]::NewLine).Trim()
$prph = @{}
foreach($p in $properties)
{
$pArr = $p -split "="
#if the value can be an int we must convert it to an int
if([bool]($pArr[1] -as [int] -is [int]))
{
$prph.Add($pArr[0].Trim(),$pArr[1].Trim() -as [int])
}
else
{
$prph.Add($pArr[0].Trim(),$pArr[1].Trim())
}
}
#create the new CimInstance
$cim = New-CimInstance -ClassName $str.Split("{")[0].Trim() -Property $prph -ClientOnly
#add the new CimInstance to the array
$KeyVal += $cim
}
$config.Property.Add($key,$KeyVal)
}
ElseIf ($prop.PropertyType -eq "[Int32]")
{
# Add Supoort for Int32
[int]$KeyValue = $attrib.Item($_)
$config.Property.Add($key,$KeyValue)
}
ElseIf ($prop.PropertyType -eq "[UInt32]")
{
# Add Support for [UInt32]
[UInt32]$KeyValue = $attrib.Item($_)
$config.Property.Add($key,$KeyValue)
}
}
}
try
{
@ -262,6 +286,4 @@ Catch
Fail-Json -obj $result -message $_[0].Exception.Message
}
#set-attr -obj $result -name "property" -value $property
Exit-Json -obj $result

View file

@ -1,25 +1,12 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2015, Trond Hindenes <trond@hindenes.com>, and others
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
# (c) 2015, Trond Hindenes <trond@hindenes.com>, and others
# Copyright (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
@ -31,70 +18,131 @@ module: win_dsc
version_added: "2.4"
short_description: Invokes a PowerShell DSC configuration
description:
- Invokes a PowerShell DSC Configuration. Requires PowerShell version 5 (February release or newer).
- Most of the parameters for this module are dynamic and will vary depending on the DSC Resource.
- See :doc:`windows_dsc` for more information on how to use this module.
- Configures a resource using PowerShell DSC.
- Requires PowerShell version 5.0 or newer.
- Most of the options for this module are dynamic and will vary depending on
the DSC Resource specified in I(resource_name).
- See :doc:`windows_dsc` for more information on how to use this module.
options:
resource_name:
description:
- The DSC Resource to use. Must be accessible to PowerShell using any of the default paths.
- The name of the DSC Resource to use.
- Must be accessible to PowerShell using any of the default paths.
required: true
module_version:
description:
- Can be used to configure the exact version of the dsc resource to be invoked.
- Useful if the target node has multiple versions installed of the module containing the DSC resource.
- If not specified, the module will follow standard Powershell convention and use the highest version available.
- Can be used to configure the exact version of the DSC resource to be
invoked.
- Useful if the target node has multiple versions installed of the module
containing the DSC resource.
- If not specified, the module will follow standard PowerShell convention
and use the highest version available.
default: latest
author: Trond Hindenes
free_form:
description:
- The M(win_dsc) module takes in multiple free form options based on the
DSC resource being invoked by I(resource_name).
- There is no option actually named C(free_form) so see the examples.
- This module will try and convert the option to the correct type required
by the DSC resource and throw a warning if it fails.
- If the type of the DSC resource option is a C(CimInstance) or
C(CimInstance[]), this means the value should be a dictionary or list
of dictionaries based on the values required by that option.
- If the type of the DSC resource option is a C(PSCredential) then there
needs to be 2 options set in the Ansible task definition suffixed with
C(_username) and C(_password).
- If the type of the DSC resource option is an array, then a list should be
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[]).
required: true
notes:
- By default there are a few builtin resources that come with PowerShell 5.0,
see U(https://docs.microsoft.com/en-us/powershell/dsc/builtinresource) for
more information on these resources.
- Custom DSC resources can be installed with M(win_psmodule) using the I(name)
option.
- 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.
author:
- Trond Hindenes (@trondhindenes)
'''
EXAMPLES = r'''
# Playbook example
- name: Extract zip file
win_dsc:
resource_name: archive
ensure: Present
path: "C:\\Temp\\zipfile.zip"
destination: "C:\\Temp\\Temp2"
- name: Extract zip file
win_dsc:
resource_name: Archive
Ensure: Present
Path: C:\Temp\zipfile.zip
Destination: C:\Temp\Temp2
- name: Invoke DSC with check mode
win_dsc:
resource_name: windowsfeature
name: telnet-client
- name: Install a Windows feature with the WindowsFeature resource
win_dsc:
resource_name: WindowsFeature
Name: telnet-client
- name: Edit HKCU reg key under specific user
win_regedit:
resource_name: Registry
Ensure: Present
Key: HKEY_CURRENT_USER\ExampleKey
ValueName: TestValue
ValueData: TestData
PsDscRunAsCredential_username: '{{ansible_user}}'
PsDscRunAsCredentual_password: '{{ansible_password}}'
no_log: true
- name: Create file with multiple attributes
win_dsc:
resource_name: File
DestinationPath: C:\ansible\dsc
Attributes: # can also be a comma separated string, e.g. 'Hidden, System'
- Hidden
- System
Ensure: Present
Type: Directory
# more complex example using custom DSC resource and dict values
- name: Setup the xWebAdministration module
win_psmodule:
name: xWebAdministration
state: present
- name: Create IIS Website with Binding and Authentication options
win_dsc:
resource_name: xWebsite
Ensure: Present
Name: DSC Website
State: Started
PhysicalPath: C:\inetpub\wwwroot
BindingInfo: # Example of a CimInstance[] DSC parameter (list of dicts)
- Protocol: https
Port: 1234
CertificateStoreName: MY
CertificateThumbprint: C676A89018C4D5902353545343634F35E6B3A659
HostName: DSCTest
IPAddress: '*'
SSLFlags: '1'
- Protocol: http
Port: 4321
IPAddress: '*'
AuthenticationInfo: # Example of a CimInstance DSC parameter (dict)
Anonymous: no
Basic: true
Digest: false
Windows: yes
'''
RETURN = r'''
resource_name:
description: The name of the invoked resource
returned: always
type: string
sample: windowsfeature
module_version:
description: The version of the dsc resource/module used.
returned: success
type: string
sample: "1.0.1"
attributes:
description: The attributes/parameters passed in to the DSC resource as key/value pairs
returned: always
type: complex
sample:
contains:
Key:
description: Attribute key
Value:
description: Attribute value
dsc_attributes:
description: The attributes/parameters as returned from the DSC engine in dict format
returned: always
type: complex
contains:
Key:
description: Attribute key
Value:
description: Attribute value
reboot_required:
description: flag returned from the DSC engine indicating whether or not the machine requires a reboot for the invoked changes to take effect
description: Flag returned from the DSC engine indicating whether or not
the machine requires a reboot for the invoked changes to take effect.
returned: always
type: boolean
sample: True