diff --git a/DSCResources/MSFT_xNetConnectionProfile/MSFT_xNetConnectionProfile.psm1 b/DSCResources/MSFT_xNetConnectionProfile/MSFT_xNetConnectionProfile.psm1 new file mode 100644 index 00000000..dfde985f --- /dev/null +++ b/DSCResources/MSFT_xNetConnectionProfile/MSFT_xNetConnectionProfile.psm1 @@ -0,0 +1,110 @@ +data LocalizedData +{ + # culture="en-US" + ConvertFrom-StringData -StringData @' + GettingNetConnectionProfile = Getting NetConnectionProfile from interface '{0}'. + TestIPv4Connectivity = IPv4Connectivity '{0}' does not match set IPv4Connectivity '{1}' + TestIPv6Connectivity = IPv6Connectivity '{0}' does not match set IPv6Connectivity '{1}' + TestNetworkCategory = NetworkCategory '{0}' does not match set NetworkCategory '{1}' + SetNetConnectionProfile = Setting NetConnectionProfile on interface '{0}' +'@ +} + + +function Get-TargetResource +{ + [OutputType([System.Collections.Hashtable])] + param + ( + [parameter(Position = 0, Mandatory = $true)] + [string] $InterfaceAlias + ) + + Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " + $($LocalizedData.GettingNetConnectionProfile) -f $InterfaceAlias + ) -join '') + + $result = Get-NetConnectionProfile -InterfaceAlias $InterfaceAlias + + return @{ + InterfaceAlias = $result.InterfaceAlias + NetworkCategory = $result.NetworkCategory + IPv4Connectivity = $result.IPv4Connectivity + IPv6Connectivity = $result.IPv6Connectivity + } +} + +function Set-TargetResource +{ + param + ( + [parameter(Mandatory = $true)] + [string] $InterfaceAlias, + + [ValidateSet('Disconnected', 'NoTraffic', 'Subnet', 'LocalNetwork', 'Internet')] + [string] $IPv4Connectivity, + + [ValidateSet('Disconnected', 'NoTraffic', 'Subnet', 'LocalNetwork', 'Internet')] + [string] $IPv6Connectivity, + + [ValidateSet('Public', 'Private')] + [string] $NetworkCategory + ) + + Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " + $($LocalizedData.SetNetConnectionProfile) -f $InterfaceAlias + ) -join '') + + Set-NetConnectionProfile @PSBoundParameters +} + + +function Test-TargetResource +{ + [OutputType([System.Boolean])] + param + ( + [parameter(Mandatory = $true)] + [string] $InterfaceAlias, + + [ValidateSet('Disconnected', 'NoTraffic', 'Subnet', 'LocalNetwork', 'Internet')] + [string] $IPv4Connectivity, + + [ValidateSet('Disconnected', 'NoTraffic', 'Subnet', 'LocalNetwork', 'Internet')] + [string] $IPv6Connectivity, + + [ValidateSet('Public', 'Private')] + [string] $NetworkCategory + ) + + $current = Get-TargetResource -InterfaceAlias $InterfaceAlias + + if ($IPv4Connectivity -ne $current.IPv4Connectivity) + { + Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " + $($LocalizedData.TestIPv4Connectivity) -f $IPv4Connectivity, $current.IPv4Connectivity + ) -join '') + + return $false + } + + if ($IPv6Connectivity -ne $current.IPv6Connectivity) + { + Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " + $($LocalizedData.TestIPv6Connectivity) -f $IPv6Connectivity, $current.IPv6Connectivity + ) -join '') + + return $false + } + + if ($NetworkCategory -ne $current.NetworkCategory) + { + Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " + $($LocalizedData.TestNetworkCategory) -f $NetworkCategory, $current.NetworkCategory + ) -join '') + + return $false + } + + return $true +} diff --git a/DSCResources/MSFT_xNetConnectionProfile/MSFT_xNetConnectionProfile.schema.mof b/DSCResources/MSFT_xNetConnectionProfile/MSFT_xNetConnectionProfile.schema.mof new file mode 100644 index 00000000..8c850d09 --- /dev/null +++ b/DSCResources/MSFT_xNetConnectionProfile/MSFT_xNetConnectionProfile.schema.mof @@ -0,0 +1,8 @@ +[ClassVersion("1.0"), FriendlyName("xNetConnectionProfile")] +class MSFT_xNetConnectionProfile : OMI_BaseResource +{ +[Key] string InterfaceAlias; +[Write,ValueMap{"Disconnected", "NoTraffic", "Subnet", "LocalNetwork", "Internet"},Values{"Disconnected", "NoTraffic", "Subnet", "LocalNetwork", "Internet"}] string IPv4Connectivity; +[Write,ValueMap{"Disconnected", "NoTraffic", "Subnet", "LocalNetwork", "Internet"},Values{"Disconnected", "NoTraffic", "Subnet", "LocalNetwork", "Internet"}] string IPv6Connectivity; +[Write,ValueMap{"Public", "Private"},Values{"Public", "Private"}] string NetworkCategory; +}; diff --git a/Examples/Sample_xNetConnectionProfile.ps1 b/Examples/Sample_xNetConnectionProfile.ps1 new file mode 100644 index 00000000..b4dddcb2 --- /dev/null +++ b/Examples/Sample_xNetConnectionProfile.ps1 @@ -0,0 +1,30 @@ +configuration Sample_xNetConnectionProfile +{ + param + ( + [parameter(Mandatory = $true)] + [string] $InterfaceAlias, + + [ValidateSet('Disconnected', 'NoTraffic', 'Subnet', 'LocalNetwork', 'Internet')] + [string] $IPv4Connectivity, + + [ValidateSet('Disconnected', 'NoTraffic', 'Subnet', 'LocalNetwork', 'Internet')] + [string] $IPv6Connectivity, + + [ValidateSet('Public', 'Private')] + [string] $NetworkCategory + ) + + Import-DscResource -Module xNetworking + + Node $NodeName + { + xNetConnectionProfile Integration_Test + { + InterfaceAlias = $InterfaceAlias + NetworkCategory = $NetworkCategory + IPv4Connectivity = $IPv4Connectivity + IPv6Connectivity = $IPv6Connectivity + } + } +} diff --git a/README.md b/README.md index f19e2478..ab8dcd35 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build status](https://ci.appveyor.com/api/projects/status/obmudad7gy8usbx2/branch/master?svg=true)](https://ci.appveyor.com/project/PowerShell/xnetworking/branch/master) +[![Build status](https://ci.appveyor.com/api/projects/status/obmudad7gy8usbx2/branch/master?svg=true)](https://ci.appveyor.com/project/PowerShell/xnetworking/branch/master) # xNetworking @@ -15,6 +15,7 @@ Please check out common DSC Resources [contributing guidelines](https://github.c * **xDnsServerAddress** sets a node's DNS server. * **xDnsConnectionSuffix** sets a node's network interface connection-specific DNS suffix. * **xDefaultGatewayAddress** sets a node's default gateway address. +* **xNetConnectionProfile** sets a node's connection profile. ### xIPAddress @@ -72,6 +73,13 @@ Please check out common DSC Resources [contributing guidelines](https://github.c * **RemoteMachine**: Specifies that matching IPsec rules of the indicated computer accounts are created. This parameter specifies that only network packets that are authenticated as incoming from or outgoing to a computer identified in the list of computer accounts (SID) match this rule. This parameter value is specified as an SDDL string. * **RemoteUser**: Specifies that matching IPsec rules of the indicated user accounts are created. This parameter specifies that only network packets that are authenticated as incoming from or outgoing to a user identified in the list of user accounts match this rule. This parameter value is specified as an SDDL string. +### xNetConnectionProfile +* **InterfaceAlias**: Specifies the alias for the Interface that is being changed. +* **NetworkCategory**: Sets the NetworkCategory for the interface - per [the documentation ](https://technet.microsoft.com/en-us/%5Clibrary/jj899565(v=wps.630).aspx) this can only be set to { Public | Private } +* **IPv4Connectivity**: Specifies the IPv4 Connection Value { Disconnected | NoTraffic | Subnet | LocalNetwork | Internet } +* **IPv6Connectivity**: Specifies the IPv6 Connection Value { Disconnected | NoTraffic | Subnet | LocalNetwork | Internet } + + ## Known Invalid Configurations ### xFirewall @@ -89,12 +97,14 @@ The cmdlet does not fully support the Inquire action for debug messages. Cmdlet ## Versions ### Unreleased Version +* Added the following resources: + * MSFT_xDNSConnectionSuffix resource to manage connection-specific DNS suffixes. + * MSFT_xNetConnectionProfile resource to manage Connection Profiles for interfaces. * MSFT_xDNSServerAddress: Corrected Verbose logging messages when multiple DNS adddressed specified. * MSFT_xDNSServerAddress: Change to ensure resource terminates if DNS Server validation fails. * MSFT_xDNSServerAddress: Added Validate parameter to enable DNS server validation when changing server addresses. * MSFT_xFirewall: ApplicationPath Parameter renamed to Program for consistency with Cmdlets. * MSFT_xFirewall: Fix to prevent error when DisplayName parameter is set on an existing rule. -* Added xDnsConnectionSuffix resource to manage connection-specific DNS suffixes. * MSFT_xFirewall: Setting a different DisplayName parameter on an existing rule now correctly reports as needs change. * MSFT_xFirewall: Changed DisplayGroup parameter to Group for consistency with Cmdlets and reduce confusion. * MSFT_xFirewall: Changing the Group of an existing Firewall rule will recreate the Firewall rule rather than change it. @@ -546,4 +556,20 @@ configuration Sample_xFirewall_AddFirewallRule_AllParameters Sample_xFirewall_AddFirewallRule_AllParameters Start-DscConfiguration -Path Sample_xFirewall_AddFirewallRule_AllParameters -Wait -Verbose -Force -``` \ No newline at end of file +``` + +### Set the NetConnectionProfile to Public + +````powershell +configuration MSFT_xNetConnectionProfile_Config { + Import-DscResource -ModuleName xNetworking + node localhost { + xNetConnectionProfile Integration_Test { + InterfaceAlias = 'Wi-Fi' + NetworkCategory = 'Public' + IPv4Connectivity = 'Internet' + IPv6Connectivity = 'Disconncted' + } + } +} +```` \ No newline at end of file diff --git a/Templates/integration_config_template.ps1 b/Templates/integration_config_template.ps1 new file mode 100644 index 00000000..ce6ffbe4 --- /dev/null +++ b/Templates/integration_config_template.ps1 @@ -0,0 +1,21 @@ +<# + This file exists so we can load the test file without necessarily having xNetworking in + the $env:PSModulePath. Otherwise PowerShell will throw an error when reading the Pester File +#> + +$rule = @{ + # TODO: Populate $rule with config data. +} + +# TODO: Modify ResourceName +configuration 'MSFT_' { + Import-DscResource -ModuleName xNetworking + node localhost { + # TODO: Modify ResourceName + '' Integration_Test { + # TODO: Fill Configuration Code Here + } + } +} + +# TODO: (Optional): Add More Configuration Templates diff --git a/Templates/integration_template.ps1 b/Templates/integration_template.ps1 index 6a1116b5..c4a3fbbe 100644 --- a/Templates/integration_template.ps1 +++ b/Templates/integration_template.ps1 @@ -3,7 +3,7 @@ Template for creating Integration Tests .DESCRIPTION To Use: - 1. Copy to \Tests\Integration\ folder and rename MSFT_x.tests.ps1 + 1. Copy to \Tests\Integration\ folder and rename MSFT_x.Integration.tests.ps1 2. Customize TODO sections. .NOTES @@ -96,7 +96,7 @@ try #> $ConfigFile = Join-Path -Path $PSScriptRoot -ChildPath "$DSCResourceName.config.ps1" . $ConfigFile - + Describe "$($DSCResourceName)_Integration" { #region DEFAULT TESTS It 'Should compile without throwing' { @@ -132,7 +132,7 @@ finally if ($rollbackExecution) { Set-ExecutionPolicy -ExecutionPolicy $executionPolicy -Force - } + } # Cleanup Working Folder if (Test-Path -Path $WorkingFolder) diff --git a/Tests/Integration/MSFT_xNetConnectionProfile.Integration.Tests.ps1 b/Tests/Integration/MSFT_xNetConnectionProfile.Integration.Tests.ps1 new file mode 100644 index 00000000..6c7e2889 --- /dev/null +++ b/Tests/Integration/MSFT_xNetConnectionProfile.Integration.Tests.ps1 @@ -0,0 +1,139 @@ +$DSCModuleName = 'xNetworking' +$DSCResourceName = 'MSFT_xNetConnectionProfile' +$RelativeModulePath = "$DSCModuleName.psd1" + +#region HEADER +# Temp Working Folder - always gets remove on completion +$WorkingFolder = Join-Path -Path $env:Temp -ChildPath $DSCResourceName + +# Copy to Program Files for WMF 4.0 Compatability as it can only find resources in a few known places. +$moduleRoot = "${env:ProgramFiles}\WindowsPowerShell\Modules\$DSCModuleName" + +# If this module already exists in the Modules folder, make a copy of it in +# the temporary folder so that it isn't accidentally used in this test. +if(-not (Test-Path -Path $moduleRoot)) +{ + $null = New-Item -Path $moduleRoot -ItemType Directory +} +else +{ + # Copy the existing folder out to the temp directory to hold until the end of the run + # Delete the folder to remove the old files. + $tempLocation = Join-Path -Path $env:Temp -ChildPath $DSCModuleName + Copy-Item -Path $moduleRoot -Destination $tempLocation -Recurse -Force + Remove-Item -Path $moduleRoot -Recurse -Force + $null = New-Item -Path $moduleRoot -ItemType Directory +} + + +# Copy the module to be tested into the Module Root +Copy-Item -Path $PSScriptRoot\..\..\* -Destination $moduleRoot -Recurse -Force -Exclude '.git' + +# Import the Module +$Splat = @{ + Path = $moduleRoot + ChildPath = $RelativeModulePath + Resolve = $true + ErrorAction = 'Stop' +} +$DSCModuleFile = Get-Item -Path (Join-Path @Splat) + +# Remove all copies of the module from memory so an old one is not used. +if (Get-Module -Name $DSCModuleFile.BaseName -All) +{ + Get-Module -Name $DSCModuleFile.BaseName -All | Remove-Module +} + +# Import the Module to test. +Import-Module -Name $DSCModuleFile.FullName -Force + +<# + This is to fix a problem in AppVoyer where we have multiple copies of the resource + in two different folders. This should probably be adjusted to be smarter about how + it finds the resources. +#> +if (($env:PSModulePath).Split(';') -ccontains $pwd.Path) +{ + $script:tempPath = $env:PSModulePath + $env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object {$_ -ne $pwd.path}) -join ';' +} + +# Preserve and set the execution policy so that the DSC MOF can be created +$executionPolicy = Get-ExecutionPolicy +if ($executionPolicy -ne 'Unrestricted') +{ + Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Force + $rollbackExecution = $true +} +#endregion + +# Using try/finally to always cleanup even if something awful happens. +try +{ + #region Integration Tests + <# + This file exists so we can load the test file without necessarily having xNetworking in + the $env:PSModulePath. Otherwise PowerShell will throw an error when reading the Pester File. + #> + $ConfigFile = Join-Path -Path $PSScriptRoot -ChildPath "$DSCResourceName.config.ps1" + . $ConfigFile + + Describe "$($DSCResourceName)_Integration" { + #region DEFAULT TESTS + It 'Should compile without throwing' { + { + [System.Environment]::SetEnvironmentVariable('PSModulePath', + $env:PSModulePath,[System.EnvironmentVariableTarget]::Machine) + Invoke-Expression -Command "$($DSCResourceName)_Config -OutputPath `$WorkingFolder" + Start-DscConfiguration -Path (Join-Path -Path $env:Temp -ChildPath $DSCResourceName) ` + -ComputerName localhost -Wait -Verbose -Force + } | Should not throw + } + + It 'should be able to call Get-DscConfiguration without throwing' { + { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should Not throw + } + #endregion + + It 'Should have set the resource and all the parameters should match' { + $current = Get-DscConfiguration | Where-Object {$_.ConfigurationName -eq 'MSFT_xNetconnectionProfile_Config'} + $rule.InterfaceAlias | Should Be $current.InterfaceAlias + $rule.NetworkCategory | Should Be $current.NetworkCategory + $rule.IPv4Connectivity | Should Be $current.IPv4Connectivity + $rule.IPv6Connectivity | Should Be $current.IPv6Connectivity + } + } + #endregion +} +finally +{ + #region FOOTER + # Set PSModulePath back to previous settings + $env:PSModulePath = $script:tempPath; + + # Restore the Execution Policy + if ($rollbackExecution) + { + Set-ExecutionPolicy -ExecutionPolicy $executionPolicy -Force + } + + # Cleanup Working Folder + if (Test-Path -Path $WorkingFolder) + { + Remove-Item -Path $WorkingFolder -Recurse -Force + } + + # Clean up after the test completes. + Remove-Item -Path $moduleRoot -Recurse -Force + + # Restore previous versions, if it exists. + if ($tempLocation) + { + $null = New-Item -Path $moduleRoot -ItemType Directory + Copy-Item -Path $tempLocation -Destination "${env:ProgramFiles}\WindowsPowerShell\Modules" -Recurse -Force + Remove-Item -Path $tempLocation -Recurse -Force + } + #endregion + + # TODO: Other Cleanup Code Goes Here... +} diff --git a/Tests/Integration/MSFT_xNetConnectionProfile.config.ps1 b/Tests/Integration/MSFT_xNetConnectionProfile.config.ps1 new file mode 100644 index 00000000..224d2c44 --- /dev/null +++ b/Tests/Integration/MSFT_xNetConnectionProfile.config.ps1 @@ -0,0 +1,13 @@ +$rule = Get-NetConnectionProfile + +configuration MSFT_xNetConnectionProfile_Config { + Import-DscResource -ModuleName xNetworking + node localhost { + xNetConnectionProfile Integration_Test { + InterfaceAlias = $rule.InterfaceAlias + NetworkCategory = $rule.NetworkCategory + IPv4Connectivity = $rule.IPv4Connectivity + IPv6Connectivity = $rule.IPv6Connectivity + } + } +} diff --git a/Tests/Unit/MSFT_xFirewall.Tests.ps1 b/Tests/Unit/MSFT_xFirewall.Tests.ps1 index 2ade1a63..573e6001 100644 --- a/Tests/Unit/MSFT_xFirewall.Tests.ps1 +++ b/Tests/Unit/MSFT_xFirewall.Tests.ps1 @@ -149,6 +149,7 @@ try } } Context 'Ensure is Present and the Firewall is Absent' { + Mock Get-FirewallRule It 'should return $false' { $result = Test-TargetResource -Name $FirewallRule.Name $result | Should Be $false @@ -965,7 +966,7 @@ finally if ($rollbackExecution) { Set-ExecutionPolicy -ExecutionPolicy $executionPolicy -Force - } + } # Cleanup Working Folder if (Test-Path -Path $WorkingFolder) diff --git a/Tests/Unit/MSFT_xNetconnectionProfile.tests.ps1 b/Tests/Unit/MSFT_xNetconnectionProfile.tests.ps1 new file mode 100644 index 00000000..83bf25ea --- /dev/null +++ b/Tests/Unit/MSFT_xNetconnectionProfile.tests.ps1 @@ -0,0 +1,132 @@ +$DSCResourceName = 'MSFT_xNetConnectionProfile' +$DSCModuleName = 'xNetworking' + +$Splat = @{ + Path = $PSScriptRoot + ChildPath = "..\..\DSCResources\$DSCResourceName\$DSCResourceName.psm1" + Resolve = $true + ErrorAction = 'Stop' +} + +$DSCResourceModuleFile = Get-Item -Path (Join-Path @Splat) + +$moduleRoot = "${env:ProgramFiles}\WindowsPowerShell\Modules\$DSCModuleName" + +if(-not (Test-Path -Path $moduleRoot)) +{ + $null = New-Item -Path $moduleRoot -ItemType Directory +} +else +{ + # Copy the existing folder out to the temp directory to hold until the end of the run + # Delete the folder to remove the old files. + $tempLocation = Join-Path -Path $env:Temp -ChildPath $DSCModuleName + Copy-Item -Path $moduleRoot -Destination $tempLocation -Recurse -Force + Remove-Item -Path $moduleRoot -Recurse -Force + $null = New-Item -Path $moduleRoot -ItemType Directory +} + +Copy-Item -Path $PSScriptRoot\..\..\* -Destination $moduleRoot -Recurse -Force -Exclude '.git' + +if (Get-Module -Name $DSCResourceName) +{ + Remove-Module -Name $DSCResourceName +} + +Import-Module -Name $DSCResourceModuleFile.FullName -Force + +InModuleScope $DSCResourceName { + Describe 'Get-TargetResource - MSFT_xNetConnectionProfile' { + Mock Get-NetConnectionProfile { + return @{ + InterfaceAlias = 'InterfaceAlias' + NetworkCategory = 'Wired' + IPv4Connectivity = 'IPv4' + IPv6Connectivity = 'IPv6' + } + } + $expected = Get-NetConnectionProfile | select -first 1 + $result = Get-TargetResource -InterfaceAlias $expected.InterfaceAlias + + It 'Should return the correct values' { + $expected.InterfaceAlias | Should Be $result.InterfaceAlias + $expected.NetworkCategory | Should Be $result.NetworkCategory + $expected.IPv4Connectivity | Should Be $result.IPv4Connectivity + $expected.IPv6Connectivity | Should Be $result.IPv6Connectivity + } + } + + Describe 'Test-TargetResource - MSFT_xNetConnectionProfile' { + $Splat = @{ + InterfaceAlias = 'Test' + NetworkCategory = 'Private' + IPv4Connectivity = 'Internet' + IPv6Connectivity = 'Disconnected' + } + + Context 'IPv4Connectivity is incorrect' { + $incorrect = $Splat.Clone() + $incorrect.IPv4Connectivity = 'Disconnected' + Mock Get-TargetResource { + return $incorrect + } + + It 'should return false' { + Test-TargetResource @Splat | should be $false + } + } + + Context 'IPv6Connectivity is incorrect' { + $incorrect = $Splat.Clone() + $incorrect.IPv6Connectivity = 'Internet' + Mock Get-TargetResource { + return $incorrect + } + + It 'should return false' { + Test-TargetResource @Splat | should be $false + } + } + + Context 'NetworkCategory is incorrect' { + $incorrect = $Splat.Clone() + $incorrect.NetworkCategory = 'Public' + Mock Get-TargetResource { + return $incorrect + } + + It 'should return false' { + Test-TargetResource @Splat | should be $false + } + } + } + + Describe 'Set-TargetResource - MSFT_xNetConnectionProfile' { + It 'Should do call all the mocks' { + $Splat = @{ + InterfaceAlias = 'Test' + NetworkCategory = 'Private' + IPv4Connectivity = 'Internet' + IPv6Connectivity = 'Disconnected' + } + + Mock Set-NetConnectionProfile {} + + Set-TargetResource @Splat + + Assert-MockCalled Set-NetConnectionProfile + } + } +} + +# Clean up after the test completes. +Remove-Item -Path $moduleRoot -Recurse -Force + +# Restore previous versions, if it exists. +if ($tempLocation) +{ + $null = New-Item -Path $moduleRoot -ItemType Directory + $script:Destination = "${env:ProgramFiles}\WindowsPowerShell\Modules" + Copy-Item -Path $tempLocation -Destination $script:Destination -Recurse -Force + Remove-Item -Path $tempLocation -Recurse -Force +}