2015-03-16 10:34:07 +00:00
#!powershell
2017-08-29 20:18:03 +00:00
# Copyright (c) 2017 Artem Zinenko <zinenkoartem@gmail.com>
# Copyright (c) 2014 Timothy Vandenbrande <timothy.vandenbrande@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
2015-03-16 10:34:07 +00:00
# WANT_JSON
# POWERSHELL_COMMON
2017-08-29 20:18:03 +00:00
function Parse-ProtocolType {
param ( $protocol )
2017-05-30 23:10:34 +00:00
2017-08-29 20:18:03 +00:00
$protocolNumber = $protocol -as [ int ]
if ( $protocolNumber -is [ int ] ) {
return $protocolNumber
}
2017-05-30 23:10:34 +00:00
2017-08-29 20:18:03 +00:00
switch -wildcard ( $protocol ) {
" tcp " { return [ System.Net.Sockets.ProtocolType ] :: Tcp -as [ int ] }
" udp " { return [ System.Net.Sockets.ProtocolType ] :: Udp -as [ int ] }
" icmpv4* " { return [ System.Net.Sockets.ProtocolType ] :: Icmp -as [ int ] }
" icmpv6* " { return [ System.Net.Sockets.ProtocolType ] :: IcmpV6 -as [ int ] }
default { throw " Unknown protocol ' $protocol '. " }
}
2017-03-24 03:01:26 +00:00
}
2017-08-29 20:18:03 +00:00
# See 'Direction' constants here: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364724(v=vs.85).aspx
function Parse-Direction {
param ( $directionStr )
2017-05-30 23:10:34 +00:00
2017-08-29 20:18:03 +00:00
switch ( $directionStr ) {
" in " { return 1 }
" out " { return 2 }
default { throw " Unknown direction ' $directionStr '. " }
2017-05-30 23:10:34 +00:00
}
}
2017-08-29 20:18:03 +00:00
# See 'Action' constants here: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364724(v=vs.85).aspx
function Parse-Action {
param ( $actionStr )
2017-03-24 03:01:26 +00:00
2017-08-29 20:18:03 +00:00
switch ( $actionStr ) {
" block " { return 0 }
" allow " { return 1 }
default { throw " Unknown action ' $actionStr '. " }
}
}
2017-03-24 03:01:26 +00:00
2017-08-29 20:18:03 +00:00
# Profile enum values: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366303(v=vs.85).aspx
function Parse-Profiles
{
2017-10-10 06:23:08 +00:00
param ( $profilesList )
2017-08-29 20:18:03 +00:00
2017-10-10 06:23:08 +00:00
$profiles = ( $profilesList | Select -uniq | ForEach {
2017-08-29 20:18:03 +00:00
switch ( $_ ) {
" domain " { return 1 }
" private " { return 2 }
" public " { return 4 }
default { throw " Unknown profile ' $_ '. " }
2017-03-24 03:01:26 +00:00
}
2017-08-29 20:18:03 +00:00
} | Measure-Object -Sum ) . Sum
2017-03-24 03:01:26 +00:00
2017-08-29 20:18:03 +00:00
if ( $profiles -eq 7 ) { return 0x7fffffff }
return $profiles
2017-03-24 03:01:26 +00:00
}
2017-08-29 20:18:03 +00:00
function Parse-InterfaceTypes
{
2017-10-10 06:23:08 +00:00
param ( $interfaceTypes )
2015-10-06 13:03:27 +00:00
2017-10-10 06:23:08 +00:00
return ( $interfaceTypes | Select -uniq | ForEach {
2017-08-29 20:18:03 +00:00
switch ( $_ ) {
" wireless " { return " Wireless " }
" lan " { return " Lan " }
" ras " { return " RemoteAccess " }
default { throw " Unknown interface type ' $_ '. " }
2015-03-16 10:34:07 +00:00
}
2017-08-29 20:18:03 +00:00
} ) -Join " , "
2017-05-30 23:10:34 +00:00
}
2015-03-16 10:34:07 +00:00
2017-08-29 20:18:03 +00:00
function Parse-EdgeTraversalOptions
{
param ( $edgeTraversalOptionsStr )
2015-03-16 10:34:07 +00:00
2017-08-29 20:18:03 +00:00
switch ( $edgeTraversalOptionsStr ) {
" yes " { return 1 }
" deferapp " { return 2 }
" deferuser " { return 3 }
default { throw " Unknown edge traversal options ' $edgeTraversalOptionsStr '. " }
2017-05-30 23:10:34 +00:00
}
}
2015-03-16 10:34:07 +00:00
2017-08-29 20:18:03 +00:00
function Parse-SecureFlags
{
param ( $secureFlagsStr )
switch ( $secureFlagsStr ) {
" authnoencap " { return 1 }
" authenticate " { return 2 }
" authdynenc " { return 3 }
" authenc " { return 4 }
default { throw " Unknown secure flags ' $secureFlagsStr '. " }
2017-05-30 23:10:34 +00:00
}
2017-08-29 20:18:03 +00:00
}
2017-05-30 23:10:34 +00:00
2017-08-29 20:18:03 +00:00
function New-FWRule
{
param (
[ string ] $name ,
[ string ] $description ,
[ string ] $applicationName ,
[ string ] $serviceName ,
[ string ] $protocol ,
[ string ] $localPorts ,
[ string ] $remotePorts ,
[ string ] $localAddresses ,
[ string ] $remoteAddresses ,
[ string ] $direction ,
[ string ] $action ,
[ bool ] $enabled ,
2017-10-10 06:23:08 +00:00
[ string[] ] $profiles ,
[ string[] ] $interfaceTypes ,
2017-08-29 20:18:03 +00:00
[ string ] $edgeTraversalOptions ,
[ string ] $secureFlags
)
# INetFwRule interface description: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365344(v=vs.85).aspx
$rule = New-Object -ComObject HNetCfg . FWRule
$rule . Name = $name
$rule . Enabled = $enabled
if ( $description ) { $rule . Description = $description }
if ( $applicationName ) { $rule . ApplicationName = $applicationName }
if ( $serviceName ) { $rule . ServiceName = $serviceName }
if ( $protocol -and $protocol -ne " any " ) { $rule . Protocol = Parse-ProtocolType -protocol $protocol }
if ( $localPorts -and $localPorts -ne " any " ) { $rule . LocalPorts = $localPorts }
if ( $remotePorts -and $remotePorts -ne " any " ) { $rule . RemotePorts = $remotePorts }
if ( $localAddresses -and $localAddresses -ne " any " ) { $rule . LocalAddresses = $localAddresses }
if ( $remoteAddresses -and $remoteAddresses -ne " any " ) { $rule . RemoteAddresses = $remoteAddresses }
if ( $direction ) { $rule . Direction = Parse-Direction -directionStr $direction }
if ( $action ) { $rule . Action = Parse-Action -actionStr $action }
2018-01-03 04:25:46 +00:00
# Profiles value cannot be a uint32, but the "all profiles" value (0x7FFFFFFF) will often become a uint32, so must cast to [int]
if ( $profiles ) { $rule . Profiles = [ int ] ( Parse-Profiles -profilesList $profiles ) }
2017-10-10 06:23:08 +00:00
if ( $interfaceTypes -and @ ( Compare-Object $interfaceTypes @ ( " any " ) ) . Count -ne 0 ) { $rule . InterfaceTypes = Parse-InterfaceTypes -interfaceTypes $interfaceTypes }
2017-08-29 20:18:03 +00:00
if ( $edgeTraversalOptions -and $edgeTraversalOptions -ne " no " ) {
# EdgeTraversalOptions property exists only from Windows 7/Windows Server 2008 R2: https://msdn.microsoft.com/en-us/library/windows/desktop/dd607256(v=vs.85).aspx
if ( $rule | Get-Member -Name 'EdgeTraversalOptions' ) {
$rule . EdgeTraversalOptions = Parse-EdgeTraversalOptions -edgeTraversalOptionsStr $edgeTraversalOptions
2017-05-30 23:10:34 +00:00
}
2017-08-29 20:18:03 +00:00
}
if ( $secureFlags -and $secureFlags -ne " notrequired " ) {
# SecureFlags property exists only from Windows 8/Windows Server 2012: https://msdn.microsoft.com/en-us/library/windows/desktop/hh447465(v=vs.85).aspx
if ( $rule | Get-Member -Name 'SecureFlags' ) {
$rule . SecureFlags = Parse-SecureFlags -secureFlagsStr $secureFlags
2015-03-16 10:34:07 +00:00
}
2017-05-30 23:10:34 +00:00
}
2017-08-29 20:18:03 +00:00
return $rule
2015-03-16 10:34:07 +00:00
}
2017-08-29 20:18:03 +00:00
$ErrorActionPreference = " Stop "
2017-05-30 23:10:34 +00:00
$result = @ {
changed = $false
}
2015-03-16 10:34:07 +00:00
2017-05-30 23:10:34 +00:00
$params = Parse-Args $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name " _ansible_check_mode " -type " bool " -default $false
$diff_support = Get-AnsibleParam -obj $params -name " _ansible_diff " -type " bool " -default $false
2015-03-16 10:34:07 +00:00
2016-06-20 21:35:27 +00:00
$name = Get-AnsibleParam -obj $params -name " name " -failifempty $true
2017-05-30 23:10:34 +00:00
$description = Get-AnsibleParam -obj $params -name " description " -type " str "
$direction = Get-AnsibleParam -obj $params -name " direction " -type " str " -failifempty $true -validateset " in " , " out "
$action = Get-AnsibleParam -obj $params -name " action " -type " str " -failifempty $true -validateset " allow " , " block " , " bypass "
$program = Get-AnsibleParam -obj $params -name " program " -type " str "
$service = Get-AnsibleParam -obj $params -name " service " -type " str "
$enabled = Get-AnsibleParam -obj $params -name " enabled " -type " bool " -default $true -aliases " enable "
2017-10-10 06:23:08 +00:00
$profiles = Get-AnsibleParam -obj $params -name " profiles " -type " list " -default @ ( " domain " , " private " , " public " ) -aliases " profile "
2017-05-30 23:10:34 +00:00
$localip = Get-AnsibleParam -obj $params -name " localip " -type " str " -default " any "
$remoteip = Get-AnsibleParam -obj $params -name " remoteip " -type " str " -default " any "
$localport = Get-AnsibleParam -obj $params -name " localport " -type " str "
$remoteport = Get-AnsibleParam -obj $params -name " remoteport " -type " str "
$protocol = Get-AnsibleParam -obj $params -name " protocol " -type " str " -default " any "
2017-10-10 06:23:08 +00:00
$interfacetypes = Get-AnsibleParam -obj $params -name " interfacetypes " -type " list " -default @ ( " any " )
2017-08-29 20:18:03 +00:00
$edge = Get-AnsibleParam -obj $params -name " edge " -type " str " -default " no " -validateset " no " , " yes " , " deferapp " , " deferuser "
$security = Get-AnsibleParam -obj $params -name " security " -type " str " -default " notrequired " -validateset " notrequired " , " authnoencap " , " authenticate " , " authdynenc " , " authenc "
2017-05-30 23:10:34 +00:00
$state = Get-AnsibleParam -obj $params -name " state " -type " str " -default " present " -validateset " present " , " absent "
2017-10-10 06:23:08 +00:00
2017-05-30 23:10:34 +00:00
$force = Get-AnsibleParam -obj $params -name " force " -type " bool " -default $false
2017-10-10 06:23:08 +00:00
if ( $force ) {
Add-DeprecationWarning -obj $result -message " 'force' isn't required anymore " -version 2.9
}
2015-03-16 10:34:07 +00:00
2017-08-29 20:18:03 +00:00
if ( $diff_support ) {
$result . diff = @ { }
$result . diff . prepared = " "
2016-06-20 21:35:27 +00:00
}
2015-03-16 10:34:07 +00:00
2017-08-29 20:18:03 +00:00
try {
$fw = New-Object -ComObject HNetCfg . FwPolicy2
2017-05-30 23:10:34 +00:00
2017-08-29 20:18:03 +00:00
$existingRule = $fw . Rules | Where { $_ . Name -eq $name }
2017-05-30 23:10:34 +00:00
2017-08-29 20:18:03 +00:00
if ( $existingRule -is [ System.Array ] ) {
Fail-Json $result " Multiple firewall rules with name ' $name ' found. "
}
2017-05-30 23:10:34 +00:00
2017-08-29 20:18:03 +00:00
$rule = New-FWRule -name $name `
-description $description `
-direction $direction `
-action $action `
-applicationName $program `
-serviceName $service `
-enabled $enabled `
-profiles $profiles `
-localAddresses $localip `
-remoteAddresses $remoteip `
-localPorts $localport `
-remotePorts $remoteport `
-protocol $protocol `
-interfaceTypes $interfacetypes `
-edgeTraversalOptions $edge `
-secureFlags $security
$fwPropertiesToCompare = @ ( 'Name' , 'Description' , 'Direction' , 'Action' , 'ApplicationName' , 'ServiceName' , 'Enabled' , 'Profiles' , 'LocalAddresses' , 'RemoteAddresses' , 'LocalPorts' , 'RemotePorts' , 'Protocol' , 'InterfaceTypes' , 'EdgeTraversalOptions' , 'SecureFlags' )
if ( $state -eq " absent " ) {
if ( $existingRule -eq $null ) {
$result . msg = " Firewall rule ' $name ' does not exist. "
} else {
if ( $diff_support ) {
foreach ( $prop in $fwPropertiesToCompare ) {
$result . diff . prepared + = " -[ $( $prop ) =' $( $existingRule . $prop ) '] `n "
}
}
2017-05-30 23:10:34 +00:00
2017-08-29 20:18:03 +00:00
if ( -not $check_mode ) {
$fw . Rules . Remove ( $existingRule . Name )
}
$result . changed = $true
$result . msg = " Firewall rule ' $name ' removed. "
2017-05-30 23:10:34 +00:00
}
2017-08-29 20:18:03 +00:00
} elseif ( $state -eq " present " ) {
if ( $existingRule -eq $null ) {
if ( $diff_support ) {
foreach ( $prop in $fwPropertiesToCompare ) {
$result . diff . prepared + = " +[ $( $prop ) =' $( $existingRule . $prop ) '] `n "
}
2015-03-16 10:34:07 +00:00
}
2017-05-30 23:10:34 +00:00
2017-08-29 20:18:03 +00:00
if ( -not $check_mode ) {
$fw . Rules . Add ( $rule )
2015-03-16 10:34:07 +00:00
}
2017-05-30 23:10:34 +00:00
$result . changed = $true
2017-08-29 20:18:03 +00:00
$result . msg = " Firewall rule ' $name ' created. "
2015-03-16 10:34:07 +00:00
} else {
2017-08-29 20:18:03 +00:00
foreach ( $prop in $fwPropertiesToCompare ) {
if ( $existingRule . $prop -ne $rule . $prop ) {
if ( $diff_support ) {
$result . diff . prepared + = " -[ $( $prop ) =' $( $existingRule . $prop ) '] `n "
$result . diff . prepared + = " +[ $( $prop ) =' $( $rule . $prop ) '] `n "
}
2017-05-30 23:10:34 +00:00
2017-08-29 20:18:03 +00:00
if ( -not $check_mode ) {
2018-01-03 04:25:46 +00:00
# Profiles value cannot be a uint32, but the "all profiles" value (0x7FFFFFFF) will often become a uint32, so must cast to [int]
# to prevent InvalidCastException under PS5+
If ( $prop -eq 'Profiles' ) {
$existingRule . Profiles = [ int ] $rule . $prop
}
Else {
$existingRule . $prop = $rule . $prop
}
2017-08-29 20:18:03 +00:00
}
$result . changed = $true
}
}
2015-03-16 10:34:07 +00:00
2017-08-29 20:18:03 +00:00
if ( $result . changed ) {
$result . msg = " Firewall rule ' $name ' changed. "
} else {
$result . msg = " Firewall rule ' $name ' already exists. "
}
2017-05-30 23:10:34 +00:00
}
}
2017-08-29 20:18:03 +00:00
} catch [ Exception ] {
2018-01-03 04:25:46 +00:00
$ex = $_
$result [ 'exception' ] = $ ( $ex | Out-String )
Fail-Json $result $ex . Exception . Message
2017-05-30 23:10:34 +00:00
}
2015-03-16 10:34:07 +00:00
2017-05-30 23:10:34 +00:00
Exit-Json $result