diff --git a/lib/ansible/modules/windows/win_netbios.ps1 b/lib/ansible/modules/windows/win_netbios.ps1 new file mode 100644 index 00000000000..0179a40260c --- /dev/null +++ b/lib/ansible/modules/windows/win_netbios.ps1 @@ -0,0 +1,72 @@ +#!powershell + +# Copyright: (c) 2019, Thomas Moore (@tmmruk) <hi@tmmr.uk> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +#AnsibleRequires -CSharpUtil Ansible.Basic + +$spec = @{ + options = @{ + state = @{ type = "str"; choices = "enabled", "disabled", "default"; required = $true } + adapter_names = @{ type = "list"; required = $false } + } + supports_check_mode = $true +} + +$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) +$module.Result.reboot_required = $false + +$state = $module.Params.state +$adapter_names = $module.Params.adapter_names + +switch ( $state ) +{ + 'default'{ $netbiosoption = 0 } + enabled { $netbiosoption = 1 } + disabled { $netbiosoption = 2 } +} + +if(-not $adapter_names) +{ + # Target all network adapters on the system + $get_params = @{ + ClassName = 'Win32_NetworkAdapterConfiguration' + Filter = 'IPEnabled=true' + Property = @('MacAddress', 'TcpipNetbiosOptions') + } + $target_adapters_config = Get-CimInstance @get_params +} +else +{ + $get_params = @{ + Class = 'Win32_NetworkAdapter' + Filter = ($adapter_names | ForEach-Object -Process { "NetConnectionId='$_'" }) -join " OR " + KeyOnly = $true + } + $target_adapters_config = Get-CimInstance @get_params | Get-CimAssociatedInstance -ResultClass 'Win32_NetworkAdapterConfiguration' + if(($target_adapters_config | Measure-Object).Count -ne $adapter_names.Count) + { + $module.FailJson("Not all of the target adapter names could be found on the system. No configuration changes have been made. $adapter_names") + } +} + +foreach($adapter in $target_adapters_config) +{ + if($adapter.TcpipNetbiosOptions -ne $netbiosoption) + { + if(-not $module.CheckMode) + { + $result = Invoke-CimMethod -InputObject $adapter -MethodName SetTcpipNetbios -Arguments @{TcpipNetbiosOptions=$netbiosoption} + switch ( $result.ReturnValue ) + { + 0 { <# Success no reboot required #> } + 1 { $module.Result.reboot_required = $true } + 100 { $module.Warn("DHCP not enabled on adapter $($adapter.MacAddress). Unable to set default. Try using disabled or enabled options instead.") } + default { $module.FailJson("An error occurred while setting TcpipNetbios options on adapter $($adapter.MacAddress). Return code $($result.ReturnValue).") } + } + } + $module.Result.changed = $true + } +} + +$module.ExitJson() diff --git a/lib/ansible/modules/windows/win_netbios.py b/lib/ansible/modules/windows/win_netbios.py new file mode 100644 index 00000000000..a3bd0dfac00 --- /dev/null +++ b/lib/ansible/modules/windows/win_netbios.py @@ -0,0 +1,80 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Thomas Moore (@tmmruk) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# this is a windows documentation stub. actual code lives in the .ps1 +# file of the same name + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = r''' +--- +module: win_netbios +version_added: '2.9' +short_description: Manage NetBIOS over TCP/IP settings on Windows. +description: + - Enables or disables NetBIOS on Windows network adapters. + - Can be used to protect a system against NBT-NS poisoning and avoid NBNS broadcast storms. + - Settings can be applied system wide or per adapter. +options: + state: + description: + - Whether NetBIOS should be enabled, disabled, or default (use setting from DHCP server or if static IP address is assigned enable NetBIOS). + choices: + - enabled + - disabled + - default + required: yes + type: str + adapter_names: + description: + - List of adapter names for which to manage NetBIOS settings. If this option is omitted then configuration is applied to all adapters on the system. + - The adapter name used is the connection caption in the Network Control Panel or via C(Get-NetAdapter), eg C(Ethernet 2). + type: list + required: no + +author: + - Thomas Moore (@tmmruk) +notes: + - Changing NetBIOS settings does not usually require a reboot and will take effect immediately. + - UDP port 137/138/139 will no longer be listening once NetBIOS is disabled. +''' + +EXAMPLES = r''' +- name: Disable NetBIOS system wide + win_netbios: + state: disabled + +- name: Disable NetBIOS on Ethernet2 + win_netbios: + state: disabled + adapter_names: + - Ethernet2 + +- name: Enable NetBIOS on Public and Backup adapters + win_netbios: + state: enabled + adapter_names: + - Public + - Backup + +- name: Set NetBIOS to system default on all adapters + win_netbios: + state: default + +''' + +RETURN = r''' +reboot_required: + description: Boolean value stating whether a system reboot is required. + returned: always + type: bool + sample: true +''' diff --git a/test/integration/targets/win_netbios/aliases b/test/integration/targets/win_netbios/aliases new file mode 100644 index 00000000000..4c08975b17d --- /dev/null +++ b/test/integration/targets/win_netbios/aliases @@ -0,0 +1 @@ +shippable/windows/group6 diff --git a/test/integration/targets/win_netbios/library/win_device.ps1 b/test/integration/targets/win_netbios/library/win_device.ps1 new file mode 100644 index 00000000000..2b69bdbf6d4 --- /dev/null +++ b/test/integration/targets/win_netbios/library/win_device.ps1 @@ -0,0 +1,541 @@ +#!powershell + +#AnsibleRequires -CSharpUtil Ansible.Basic +#Requires -Module Ansible.ModuleUtils.AddType + +$spec = @{ + options = @{ + hardware_id = @{ type = "str" } + name = @{ type = "str" } + path = @{ type = "path" } + state = @{ type = "str"; choices = @("absent", "present"); default = "present" } + } + required_if = @( + @("state", "present", @("path", "hardware_id"), $true), + @("state", "absent", @(,"name")) + ) + supports_check_mode = $true +} + +$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) + +$hardware_id = $module.Params.hardware_id +$name = $module.Params.name +$path = $module.Params.path +$state = $module.Params.state + +$module.Result.reboot_required = $false + +Add-CSharpType -References @' +using Microsoft.Win32.SafeHandles; +using System; +using System.ComponentModel; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ansible.Device +{ + public class NativeHelpers + { + [StructLayout(LayoutKind.Sequential)] + public class SP_DEVINFO_DATA + { + public UInt32 cbSize; + public Guid ClassGuid; + public UInt32 DevInst; + public IntPtr Reserved; + + public SP_DEVINFO_DATA() + { + this.cbSize = (UInt32)Marshal.SizeOf(this); + this.ClassGuid = Guid.Empty; + } + } + + [Flags] + public enum DeviceInfoCreationFlags : uint + { + DICD_GENERATE_ID = 0x00000001, + DICD_INHERIT_CLASSDRVS = 0x00000002, + } + + public enum DeviceProperty : uint + { + SPDRP_DEVICEDESC = 0x0000000, + SPDRP_HARDWAREID = 0x0000001, + SPDRP_COMPATIBLEIDS = 0x0000002, + SPDRP_UNUSED0 = 0x0000003, + SPDRP_SERVICE = 0x0000004, + SPDRP_UNUSED1 = 0x0000005, + SPDRP_UNUSED2 = 0x0000006, + SPDRP_CLASS = 0x0000007, // Read only - tied to ClassGUID + SPDRP_CLASSGUID = 0x0000008, + SPDRP_DRIVER = 0x0000009, + SPDRP_CONFIGFLAGS = 0x000000a, + SPDRP_MFG = 0x000000b, + SPDRP_FRIENDLYNAME = 0x000000c, + SPDRP_LOCATION_INFORMATION = 0x000000d, + SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = 0x000000e, // Read only + SPDRP_CAPABILITIES = 0x000000f, // Read only + SPDRP_UI_NUMBER = 0x0000010, // Read only + SPDRP_UPPERFILTERS = 0x0000011, + SPDRP_LOWERFILTERS = 0x0000012, + SPDRP_BUSTYPEGUID = 0x0000013, // Read only + SPDRP_LEGACYBUSTYPE = 0x0000014, // Read only + SPDRP_BUSNUMBER = 0x0000015, // Read only + SPDRP_ENUMERATOR_NAME = 0x0000016, // Read only + SPDRP_SECURITY = 0x0000017, + SPDRP_SECURITY_SDS = 0x0000018, + SPDRP_DEVTYPE = 0x0000019, + SPDRP_EXCLUSIVE = 0x000001a, + SPDRP_CHARACTERISTICS = 0x000001b, + SPDRP_ADDRESS = 0x000001c, // Read only + SPDRP_UI_NUMBER_DESC_FORMAT = 0x000001d, + SPDRP_DEVICE_POWER_DATA = 0x000001e, // Read only + SPDRP_REMOVAL_POLICY = 0x000001f, // Read only + SPDRP_REMOVAL_POLICY_HW_DEFAULT = 0x0000020, // Read only + SPDRP_REMOVAL_POLICY_OVERRIDE = 0x0000021, + SPDRP_INSTALL_STATE = 0x0000022, // Read only + SPDRP_LOCATION_PATHS = 0x0000023, // Read only + SPDRP_BASE_CONTAINERID = 0x0000024, // Read only + } + + // https://docs.microsoft.com/en-us/previous-versions/ff549793%28v%3dvs.85%29 + public enum DifCodes : uint + { + DIF_SELECTDIVE = 0x00000001, + DIF_INSTALLDEVICE = 0x00000002, + DIF_ASSIGNRESOURCES = 0x00000003, + DIF_PROPERTIES = 0x00000004, + DIF_REMOVE = 0x00000005, + DIF_FIRSTTIMESETUP = 0x00000006, + DIF_FOUNDDEVICE = 0x00000007, + DIF_SELECTCLASSDRIVERS = 0x00000008, + DIF_VALIDATECLASSDRIVERS = 0x00000009, + DIF_INSTALLCLASSDRIVERS = 0x0000000a, + DIF_CALCDISKSPACE = 0x0000000b, + DIF_DESTROYPRIVATEDATA = 0x0000000c, + DIF_VALIDATEDRIVER = 0x0000000d, + DIF_DETECT = 0x0000000f, + DIF_INSTALLWIZARD = 0x00000010, + DIF_DESTROYWIZARDDATA = 0x00000011, + DIF_PROPERTYCHANGE = 0x00000012, + DIF_ENABLECLASS = 0x00000013, + DIF_DETECTVERIFY = 0x00000014, + DIF_INSTALLDEVICEFILES = 0x00000015, + DIF_UNREMOVE = 0x00000016, + DIF_SELECTBESTCOMPATDRV = 0x00000017, + DIF_ALLOW_INSTALL = 0x00000018, + DIF_REGISTERDEVICE = 0x00000019, + DIF_NEWDEVICEWIZARD_PRESELECT = 0x0000001a, + DIF_NEWDEVICEWIZARD_SELECT = 0x0000001b, + DIF_NEWDEVICEWIZARD_PREANALYZE = 0x0000001c, + DIF_NEWDEVICEWIZARD_POSTANALYZE = 0x0000001d, + DIF_NEWDEVICEWIZARD_FINISHINSTALL = 0x0000001e, + DIF_UNUSED1 = 0x0000001e, + DIF_INSTALLINTERFACES = 0x00000020, + DIF_DETECTCANCEL = 0x00000021, + DIF_REGISTER_COINSTALLERS = 0x00000022, + DIF_ADDPROPERTYPAGE_ADVANCED = 0x00000023, + DIF_ADDPROPERTYPAGE_BASIC = 0x00000024, + DIF_RESERVED1 = 0x00000025, + DIF_TROUBLESHOOTER = 0x00000026, + DIF_POWERMESSAGEWAKE = 0x00000027, + DIF_ADDREMOTEPROPERTYPAGE_ADVANCED = 0x00000028, + DIF_UPDATEDRIVER_UI = 0x00000029, + DIF_FINISHINSTALL_ACTION = 0x0000002a, + } + + [Flags] + public enum GetClassFlags : uint + { + DIGCF_DEFAULT = 0x00000001, + DIGCF_PRESENT = 0x00000002, + DIGCF_ALLCLASSES = 0x00000004, + DIGCF_PROFILE = 0x00000008, + DIGCF_DEVICEINTERFACE = 0x00000010, + } + + [Flags] + public enum InstallFlags : uint + { + INSTALLFLAG_FORCE = 0x00000001, + INSTALLFLAG_READONLY = 0x00000002, + INSTALLFLAG_NONINTERACTIVE = 0x00000004, + INSTALLFLAG_BITS = 0x00000007, + } + } + + public class NativeMethods + { + [DllImport("Setupapi.dll", SetLastError = true)] + public static extern bool SetupDiCallClassInstaller( + NativeHelpers.DifCodes InstallFunction, + SafeDeviceInfoSet DeviceInfoSet, + NativeHelpers.SP_DEVINFO_DATA DeviceInfoData); + + [DllImport("Setupapi.dll", SetLastError = true)] + public static extern SafeDeviceInfoSet SetupDiCreateDeviceInfoList( + Guid ClassGuid, + IntPtr hwndParent); + + [DllImport("Setupapi.dll", SetLastError = true)] + public static extern bool SetupDiCreateDeviceInfoW( + SafeDeviceInfoSet DeviceInfoSet, + [MarshalAs(UnmanagedType.LPWStr)] string DeviceName, + Guid ClassGuid, + [MarshalAs(UnmanagedType.LPWStr)] string DeviceDescription, + IntPtr hwndParent, + NativeHelpers.DeviceInfoCreationFlags CreationFlags, + NativeHelpers.SP_DEVINFO_DATA DeviceInfoData); + + [DllImport("Setupapi.dll", SetLastError = true)] + public static extern bool SetupDiDestroyDeviceInfoList( + IntPtr DeviceInfoSet); + + [DllImport("Setupapi.dll", SetLastError = true)] + public static extern bool SetupDiEnumDeviceInfo( + SafeDeviceInfoSet DeviceInfoSet, + UInt32 MemberIndex, + NativeHelpers.SP_DEVINFO_DATA DeviceInfoData); + + [DllImport("Setupapi.dll", SetLastError = true)] + public static extern SafeDeviceInfoSet SetupDiGetClassDevsW( + Guid ClassGuid, + [MarshalAs(UnmanagedType.LPWStr)] string Enumerator, + IntPtr hwndParent, + NativeHelpers.GetClassFlags Flags); + + [DllImport("Setupapi.dll", SetLastError = true)] + public static extern bool SetupDiGetDeviceRegistryPropertyW( + SafeDeviceInfoSet DeviceInfoSet, + NativeHelpers.SP_DEVINFO_DATA DeviceInfoData, + NativeHelpers.DeviceProperty Property, + out UInt32 PropertyRegDataType, + SafeMemoryBuffer PropertyBuffer, + UInt32 PropertyBufferSize, + ref UInt32 RequiredSize); + + [DllImport("Setupapi.dll", SetLastError = true)] + public static extern bool SetupDiGetINFClassW( + [MarshalAs(UnmanagedType.LPWStr)] string InfName, + ref Guid ClassGuid, + [MarshalAs(UnmanagedType.LPWStr)] StringBuilder ClassName, + UInt32 ClassNameSize, + ref UInt32 RequiredSize); + + [DllImport("Setupapi.dll", SetLastError = true)] + public static extern bool SetupDiSetDeviceRegistryPropertyW( + SafeDeviceInfoSet DeviceInfoSet, + NativeHelpers.SP_DEVINFO_DATA DeviceInfoData, + NativeHelpers.DeviceProperty Property, + SafeMemoryBuffer PropertyBuffer, + UInt32 PropertyBufferSize); + + [DllImport("Newdev.dll", SetLastError = true)] + public static extern bool UpdateDriverForPlugAndPlayDevicesW( + IntPtr hwndParent, + [MarshalAs(UnmanagedType.LPWStr)] string HardwareId, + [MarshalAs(UnmanagedType.LPWStr)] string FullInfPath, + NativeHelpers.InstallFlags InstallFlags, + ref bool bRebootRequired); + } + + public class SafeDeviceInfoSet : SafeHandleZeroOrMinusOneIsInvalid + { + public SafeDeviceInfoSet() : base(true) { } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + protected override bool ReleaseHandle() + { + return NativeMethods.SetupDiDestroyDeviceInfoList(handle); + } + } + + public class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid + { + public int Length = 0; + + public SafeMemoryBuffer() : base(true) { } + + public SafeMemoryBuffer(int cb) : base(true) + { + Length = cb; + base.SetHandle(Marshal.AllocHGlobal(cb)); + } + + public SafeMemoryBuffer(string sz) : base(true) + { + Length = sz.Length * sizeof(char); + base.SetHandle(Marshal.StringToHGlobalUni(sz)); + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + protected override bool ReleaseHandle() + { + Marshal.FreeHGlobal(handle); + return true; + } + } + + public class DeviceUtil + { + public static string GetDeviceFriendlyName(SafeDeviceInfoSet devInfoSet, NativeHelpers.SP_DEVINFO_DATA devInfo) + { + string friendlyName = GetDeviceStringProp(devInfoSet, devInfo, NativeHelpers.DeviceProperty.SPDRP_FRIENDLYNAME); + + // Older Windows versions may not have a friendly name set. This seems to be the case when the device has + // a unique description so we fallback to that value. + if (null == friendlyName) + friendlyName = GetDeviceStringProp(devInfoSet, devInfo, NativeHelpers.DeviceProperty.SPDRP_DEVICEDESC); + + return friendlyName; + } + + public static void SetDeviceHardwareId(SafeDeviceInfoSet devInfoSet, NativeHelpers.SP_DEVINFO_DATA devInfo, + string hardwareId) + { + SetDeviceStringProp(devInfoSet, devInfo, NativeHelpers.DeviceProperty.SPDRP_HARDWAREID, hardwareId); + } + + private static string GetDeviceStringProp(SafeDeviceInfoSet devInfoSet, NativeHelpers.SP_DEVINFO_DATA devInfo, + NativeHelpers.DeviceProperty property) + { + using (SafeMemoryBuffer memBuf = GetDeviceProperty(devInfoSet, devInfo, property)) + { + if (memBuf.IsInvalid) // Property does not exist so just return null. + return null; + + return Marshal.PtrToStringUni(memBuf.DangerousGetHandle()); + } + } + + private static SafeMemoryBuffer GetDeviceProperty(SafeDeviceInfoSet devInfoSet, + NativeHelpers.SP_DEVINFO_DATA devInfo, NativeHelpers.DeviceProperty property) + { + UInt32 requiredSize = 0; + UInt32 regDataType = 0; + if (!NativeMethods.SetupDiGetDeviceRegistryPropertyW(devInfoSet, devInfo, property, + out regDataType, new SafeMemoryBuffer(0), 0, ref requiredSize)) + { + int errCode = Marshal.GetLastWin32Error(); + if (errCode == 0x0000000D) // ERROR_INVALID_DATA + return new SafeMemoryBuffer(); // The FRIENDLYNAME property does not exist + else if (errCode != 0x0000007A) // ERROR_INSUFFICIENT_BUFFER + throw new Win32Exception(errCode); + } + + SafeMemoryBuffer memBuf = new SafeMemoryBuffer((int)requiredSize); + if (!NativeMethods.SetupDiGetDeviceRegistryPropertyW(devInfoSet, devInfo, property, + out regDataType, memBuf, requiredSize, ref requiredSize)) + { + int errCode = Marshal.GetLastWin32Error(); + memBuf.Dispose(); + + throw new Win32Exception(errCode); + } + + return memBuf; + } + + private static void SetDeviceStringProp(SafeDeviceInfoSet devInfoSet, NativeHelpers.SP_DEVINFO_DATA devInfo, + NativeHelpers.DeviceProperty property, string value) + { + using (SafeMemoryBuffer buffer = new SafeMemoryBuffer(value)) + SetDeviceProperty(devInfoSet, devInfo, property, buffer); + } + + private static void SetDeviceProperty(SafeDeviceInfoSet devInfoSet, NativeHelpers.SP_DEVINFO_DATA devInfo, + NativeHelpers.DeviceProperty property, SafeMemoryBuffer buffer) + { + if (!NativeMethods.SetupDiSetDeviceRegistryPropertyW(devInfoSet, devInfo, property, buffer, + (UInt32)buffer.Length)) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + } + } +} +'@ + +Function Get-Win32ErrorMessage { + Param ([System.Int32]$ErrorCode) + + $exp = New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList $ErrorCode + return ("{0} (Win32 ErrorCode {1} - 0x{1:X8}" -f $exp.Message, $ErrorCode) +} + +# Determine if the device is already installed +$dev_info_set = [Ansible.Device.NativeMethods]::SetupDiGetClassDevsW( + [Guid]::Empty, + [NullString]::Value, + [System.IntPtr]::Zero, + [Ansible.Device.NativeHelpers+GetClassFlags]::DIGCF_ALLCLASSES +); $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() + +try { + if ($dev_info_set.IsInvalid) { + $msg = Get-Win32ErrorMessage -ErrorCode $err + $module.FailJson("Failed to get device information set for installed devices: $msg") + } + + $dev_info = $null + if ($null -ne $name) { + # Loop through the set of all devices and compare the name + $idx = 0 + while ($true) { + $dev_info = New-Object -TypeName Ansible.Device.NativeHelpers+SP_DEVINFO_DATA + $res = [Ansible.Device.NativeMethods]::SetupDiEnumDeviceInfo( + $dev_info_set, + $idx, + $dev_info + ); $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() + + if (-not $res) { + $dev_info = $null + if ($err -eq 0x00000103) { # ERROR_NO_MORE_ITEMS + break + } + + $msg = Get-Win32ErrorMessage -ErrorCode $err + $module.FailJson("Failed to enumerate device information set at index $($idx): $msg") + } + + $device_name = [Ansible.Device.DeviceUtil]::GetDeviceFriendlyName($dev_info_set, $dev_info) + if ($device_name -eq $name) { + break + } + + $dev_info = $null + $idx++ + } + } + + if ($state -eq "absent" -and $null -ne $dev_info) { + if (-not $module.CheckMode) { + $res = [Ansible.Device.NativeMethods]::SetupDiCallClassInstaller( + [Ansible.Device.NativeHelpers+DifCodes]::DIF_REMOVE, + $dev_info_set, + $dev_info + ); $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() + + if (-not $res) { + $msg = Get-Win32ErrorMessage -ErrorCode $err + $module.FailJson("Failed to remove device $($name): $msg") + } + } + + $module.Result.changed = $true + } elseif ($state -eq "present" -and $null -eq $dev_info) { + # Populate the class guid and display name if the path to an inf file was set. + $class_id = [Guid]::Empty + $class_name = $null + if ($path) { + if (-not (Test-Path -LiteralPath $path)) { + $module.FailJson("Could not find the inf file specified at '$path'") + } + + $class_name_sb = New-Object -TypeName System.Text.StringBuilder -ArgumentList 32 # MAX_CLASS_NAME_LEN + $required_size = 0 + $res = [Ansible.Device.NativeMethods]::SetupDiGetINFClassW( + $path, + [ref]$class_id, + $class_name_sb, + $class_name_sb.Capacity, + [ref]$required_size + ); $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() + + if (-not $res) { + $msg = Get-Win32ErrorMessage -ErrorCode $err + $module.FailJson("Failed to parse driver inf at '$path': $msg") + } + + $class_name = $class_name_sb.ToString() + } + + # When creating a new device we want to start with a blank device information set. + $dev_info_set.Dispose() + + $dev_info_set = [Ansible.Device.NativeMethods]::SetupDiCreateDeviceInfoList( + $class_id, + [System.IntPtr]::Zero + ); $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() + + if ($dev_info_set.IsInvalid) { + $msg = Get-Win32ErrorMessage -ErrorCode $err + $module.FailJson("Failed to create device info set for the class $($class_id): $msg") + } + + # Create the new device element and add it to the device info set + $dev_info = New-Object -TypeName Ansible.Device.NativeHelpers+SP_DEVINFO_DATA + $res = [Ansible.Device.NativeMethods]::SetupDiCreateDeviceInfoW( + $dev_info_set, + $class_name, + $class_id, + $null, + [System.IntPtr]::Zero, + [Ansible.Device.NativeHelpers+DeviceInfoCreationFlags]::DICD_GENERATE_ID, + $dev_info + ); $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() + + if (-not $res) { + $msg = Get-Win32ErrorMessage -ErrorCode $err + $module.FailJson("Failed to create new device element for class $($class_name): $msg") + } + + # Set the hardware id of the new device so we can load the proper driver. + [Ansible.Device.DeviceUtil]::SetDeviceHardwareId($dev_info_set, $dev_info, $hardware_id) + + if (-not $module.CheckMode) { + # Install the device + $res = [Ansible.Device.NativeMethods]::SetupDiCallClassInstaller( + [Ansible.Device.NativeHelpers+DifCodes]::DIF_REGISTERDEVICE, + $dev_info_set, + $dev_info + ); $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() + + if (-not $res) { + $msg = Get-Win32ErrorMessage -ErrorCode $err + $module.FailJson("Failed to register new device for class $($class_name): $msg") + } + + # Load the drivers for the new device + $reboot_required = $false + $res = [Ansible.Device.NativeMethods]::UpdateDriverForPlugAndPlayDevicesW( + [System.IntPtr]::Zero, + $hardware_id, + $path, + [Ansible.Device.NativeHelpers+InstallFlags]'INSTALLFLAG_FORCE, INSTALLFLAG_NONINTERACTIVE', + [ref]$reboot_required + ); $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() + + if (-not $res) { + # On a failure make sure we cleanup the "installed" device + [Ansible.Device.NativeMethods]::SetupDiCallClassInstaller( + [Ansible.Device.NativeHelpers+DifCodes]::DIF_REMOVE, + $dev_info_set, + $dev_info + ) > $null + + $msg = Get-Win32ErrorMessage -ErrorCode $err + $module.FailJson("Failed to update device driver: $msg") + } + + $module.Result.reboot_required = $reboot_required + + # Now get the name of the newly created device which we return back to Ansible. + $name = [Ansible.Device.DeviceUtil]::GetDeviceFriendlyName($dev_info_set, $dev_info) + } else { + # Generate random name for check mode output + $name = "Check mode generated device for $($class_name)" + } + $module.Result.changed = $true + } +} finally { + $dev_info_set.Dispose() +} + +$module.Result.name = $name + +$module.ExitJson() diff --git a/test/integration/targets/win_netbios/tasks/main.yml b/test/integration/targets/win_netbios/tasks/main.yml new file mode 100644 index 00000000000..01f5b435b4a --- /dev/null +++ b/test/integration/targets/win_netbios/tasks/main.yml @@ -0,0 +1,54 @@ +# Test code for win_netbios module +# Copyright: (c) 2019, Thomas Moore <hi@tmmr.uk> + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# 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/>. + + +- name: ensure netbios is set to default to start with + win_netbios: + state: default + +- name: create dummy network adapter device + win_device: + path: '%WinDir%\Inf\netloop.inf' + hardware_id: '*msloop' + state: present + register: test_device_name + +- set_fact: + test_device_name: '{{ test_device_name.name }}' + +- block: + - name: get name of the dummy network adapter + win_shell: (Get-CimInstance -Class Win32_NetworkAdapter -Filter "Name='{{ test_device_name }}'").NetConnectionID + changed_when: False + register: test_adapter + + - set_fact: + test_adapter: '{{ test_adapter.stdout | trim }}' + + - name: run tests + include_tasks: tests.yml + + always: + - name: remove dummy network adapter device + win_device: + name: '{{ test_device_name }}' + state: absent + + - name: set netbios back to default after tests + win_netbios: + state: default \ No newline at end of file diff --git a/test/integration/targets/win_netbios/tasks/tests.yml b/test/integration/targets/win_netbios/tasks/tests.yml new file mode 100644 index 00000000000..9527ab691a4 --- /dev/null +++ b/test/integration/targets/win_netbios/tasks/tests.yml @@ -0,0 +1,159 @@ +# Test code for win_netbios module +# Copyright: (c) 2019, Thomas Moore <hi@tmmr.uk> + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# 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/>. + +- set_fact: + get_netbios_script: | + $adapter = Get-CimInstance -ClassName Win32_NetworkAdapter -Filter "NetConnectionID='{{ test_adapter }}'" + $config = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter "Index=$($adapter.DeviceID)" + $config.TcpipNetbiosOptions + +- name: disable netbios single adapter (check mode) + win_netbios: + adapter_names: '{{ test_adapter }}' + state: disabled + register: set_single_check + check_mode: yes + +- name: get result of disable a single adapter test (check mode) + win_shell: '{{ get_netbios_script }}' + changed_when: no + register: set_single_actual_check + +- name: assert disable a single adapter (check mode) + assert: + that: + - set_single_check is changed + - set_single_actual_check.stdout_lines == ["0"] + +- name: disable netbios single adapter + win_netbios: + adapter_names: '{{ test_adapter }}' + state: disabled + register: set_single + +- name: get result of disable a single adapter test + win_shell: '{{ get_netbios_script }}' + changed_when: no + register: set_single_actual + +- name: assert disable a single adapter + assert: + that: + - set_single_check is changed + - set_single_actual.stdout_lines == ["2"] + +- name: fail with invalid network adapter name + win_netbios: + state: disabled + adapter_names: + - FakeAdapterName + register: invalid_adapter + failed_when: invalid_adapter.msg != "Not all of the target adapter names could be found on the system. No configuration changes have been made. FakeAdapterName" + +- name: disable netbios all adapters (check mode) + win_netbios: + state: disabled + check_mode: yes + register: disable_check + +- name: assert disable netbios (check mode) + assert: + that: + - disable_check.changed + +- name: disable netbios all adapters + win_netbios: + state: disabled + register: netbios_disable + +- name: assert netbios disabled + assert: + that: + - netbios_disable.changed + +- name: test disable idempotence + win_netbios: + state: disabled + register: netbios_disable + +- name: test disable idempotence + assert: + that: + - not netbios_disable.changed + +- name: enable netbios all adapters (check mode) + win_netbios: + state: enabled + check_mode: yes + register: enable_check + +- name: assert enable netbios all adapters (check mode) + assert: + that: + - enable_check.changed + +- name: enable netbios all adapters + win_netbios: + state: enabled + register: netbios_enable + +- name: assert netbios enabled + assert: + that: + - netbios_enable.changed + +- name: test enable idempotence + win_netbios: + state: enabled + register: netbios_enable + +- name: assert enable idempotence + assert: + that: + - not netbios_enable.changed + +- name: default netbios all adapters (check mode) + win_netbios: + state: default + check_mode: yes + register: default_check + +- name: assert default netbios (check mode) + assert: + that: + - default_check.changed + +- name: default netbios all adapters + win_netbios: + state: default + register: default_enable + +- name: assert netbios default all adapters + assert: + that: + - default_enable.changed + +- name: test default idempotence + win_netbios: + state: default + register: netbios_default + +- name: assert default idempotence + assert: + that: + - not netbios_default.changed \ No newline at end of file