@ -20,12 +20,30 @@
# WANT_JSON
# POWERSHELL_COMMON
# TODO: Reimplement this using Powershell cmdlets
$ErrorActionPreference = " Stop "
function convertToNetmask($maskLength ) {
[ IPAddress ] $ip = 0 ;
[ IPAddress ] $ip = 0
$ip . Address = ( [ UInt32 ] :: MaxValue ) -shl ( 32 - $maskLength ) -shr ( 32 - $maskLength )
return $ip . IPAddressToString
}
function ConvertTo-TitleCase($string ) {
return ( Get-Culture ) . TextInfo . ToTitleCase ( $string . ToLower ( ) )
}
function ConvertTo-SortedKV($object , $unsupported = @ ( ) ) {
$output = " "
foreach ( $item in $object . GetEnumerator ( ) | Sort -Property Name ) {
if ( ( $item . Name -notin $unsupported ) -and ( $item . Value -ne $null ) ) {
$output + = " $( $item . Name ) : $( $item . Value ) `n "
}
}
return $output
}
function preprocessAndCompare($key , $outputValue , $fwsettingValue ) {
if ( $key -eq 'RemoteIP' ) {
if ( $outputValue -eq $fwsettingValue ) {
@ -54,351 +72,382 @@ function preprocessAndCompare($key, $outputValue, $fwsettingValue) {
}
}
}
elseif ( $key -eq 'Profiles' ) {
if ( ( $fwsettingValue -eq " any " ) -and ( $outputValue -eq " Domain,Private,Public " ) ) {
return $true
}
}
return $false
}
function getFirewallRule ( $fwsettings ) {
try {
$diff = $false
$result = @ {
changed = $false
identical = $false
exists = $false
failed = $false
msg = @ ( )
multiple = $false
}
#$output = Get-NetFirewallRule -name $($fwsettings.'Rule Name');
$rawoutput = @ ( netsh advfirewall firewall show rule name = " $( $fwsettings . 'Rule Name' ) " verbose )
if ( ! ( $rawoutput -eq 'No rules match the specified criteria.' ) ) {
$rawoutput | Where { $_ -match '^([^:]+):\s*(\S.*)$' } | Foreach -Begin {
$FirstRun = $true ;
$HashProps = @ { } ;
try {
$command = " netsh advfirewall firewall show rule name= `" $( $fwsettings . 'Rule Name' ) `" verbose "
#$output = Get-NetFirewallRule -name $($fwsettings.'Rule Name')
$result . output = Invoke-Expression $command | Where { $_ }
$rc = $LASTEXITCODE
if ( $rc -eq 1 ) {
$result . msg + = @ ( " No rule ' $name ' could be found " )
} elseif ( $rc -eq 0 ) {
# Process command output
$result . output | Where { $_ -match '^([^:]+):\s*(\S.*)$' } | ForEach -Begin {
$FirstRun = $true
$HashProps = @ { }
} -Process {
if ( ( $Matches [ 1 ] -eq 'Rule Name' ) -and ( ! ( $FirstRun ) ) ) {
#$output=New-Object -TypeName PSCustomObject -Property $HashProps;
$output = $HashProps ;
$HashProps = @ { } ;
} ;
$HashProps . $ ( $Matches [ 1 ] ) = $Matches [ 2 ] ;
$FirstRun = $false ;
} -End {
#$output=New-Object -TypeName PSCustomObject -Property $HashProps;
$output = $HashProps ;
if ( ( $Matches [ 1 ] -eq 'Rule Name' ) -and ( -not $FirstRun ) ) {
$output = $HashProps
$HashProps = @ { }
}
$HashProps . $ ( $Matches [ 1 ] ) = $Matches [ 2 ]
$FirstRun = $false
} -End {
$output = $HashProps
}
$exists = $false ;
$correct = $true ;
$diff = $false ;
$multi = $false ;
$correct = $false ;
$difference = @ ( ) ;
$msg = @ ( ) ;
if ( $ ( $output | measure ) . count -gt 0 ) {
$exists = $true ;
$msg + = @ ( " The rule ' " + $fwsettings . 'Rule Name' + " ' exists. " ) ;
$diff = $false
$result . exists = $true
#$result.msg += @("The rule '$($fwsettings.'Rule Name')' exists.")
if ( $ ( $output | measure ) . count -gt 1 ) {
$multi = $true
$msg + = @ ( " The rule ' " + $fwsettings . 'Rule Name' + " ' has multiple entries. " ) ;
ForEach ( $rule in $output . GetEnumerator ( ) ) {
ForEach ( $fwsetting in $fwsettings . GetEnumerator ( ) ) {
if ( $rule . $fwsetting -ne $fwsettings . $fwsetting ) {
$diff = $true ;
#$difference+=@($fwsettings.$($fwsetting.Key));
$difference + = @ ( " output: $rule . $fwsetting ,fwsetting: $fwsettings . $fwsetting " ) ;
} ;
} ;
if ( $diff -eq $false ) {
$correct = $true
} ;
} ;
$result . multiple = $true
$result . msg + = @ ( " The rule ' $( $fwsettings . 'Rule Name' ) ' has multiple entries. " )
$result . diff = @ { }
$result . diff . after = ConvertTo-SortedKV $fwsettings
$result . diff . before = ConvertTo-SortedKV $rule $unsupported
if ( $result . diff . after -ne $result . diff . before ) {
$diff = $true
}
} else {
if ( $diff_support ) {
$result . diff = @ { }
$result . diff . after = ConvertTo-SortedKV $fwsettings
$result . diff . before = ConvertTo-SortedKV $output $unsupported
}
ForEach ( $fwsetting in $fwsettings . GetEnumerator ( ) ) {
if ( $output . $ ( $fwsetting . Key ) -ne $fwsettings . $ ( $fwsetting . Key ) ) {
if ( ( preprocessAndCompare -key $fwsetting . Key -outputValue $output . $ ( $fwsetting . Key ) -fwsettingValue $fwsettings . $ ( $fwsetting . Key ) ) ) {
Continue
} elseif ( ( $fwsetting . Key -eq 'DisplayName' ) -and ( $output . " Rule Name " -eq $fwsettings . $ ( $fwsetting . Key ) ) ) {
Continue
} elseif ( ( $fwsetting . Key -eq 'Program' ) -and ( $output . $ ( $fwsetting . Key ) -eq ( Expand-Environment ( $fwsettings . $ ( $fwsetting . Key ) ) ) ) ) {
# Ignore difference caused by expanded environment variables
Continue
} else {
$diff = $true ;
$difference + = @ ( $fwsettings . $ ( $fwsetting . Key ) ) ;
} ;
} ;
} ;
if ( $diff -eq $false ) {
$correct = $true
} ;
} ;
if ( $correct ) {
$ msg + = @ ( " An identical rule exists" ) ;
$diff = $true
Break
}
}
}
}
if ( -not $diff ) {
$result . identical = $true
}
if ( $result . identical ) {
$ result. msg + = @ ( " The rule '$name ' exists and is identical " )
} else {
$msg + = @ ( " The rule exists but has different values " ) ;
$result . msg + = @ ( " The rule ' $name ' exists but has different values " )
}
}
} else {
$msg + = @ ( " No rule could be found " ) ;
} ;
$result = @ {
failed = $false
exists = $exists
identical = $correct
multiple = $multi
difference = $difference
msg = $msg
$result . failed = $true
}
} catch [ Exception ] {
$result = @ {
failed = $true
error = $_ . Exception . Message
msg = $msg
} catch [ Exception ] {
$result . failed = $true
$result . error = $_ . Exception . Message
}
} ;
return $result
} ;
}
function createFireWallRule ( $fwsettings ) {
$msg = @ ( )
$execString = " netsh advfirewall firewall add rule "
$result = @ {
changed = $false
failed = $false
msg = @ ( )
}
$command = " netsh advfirewall firewall add rule "
ForEach ( $fwsetting in $fwsettings . GetEnumerator ( ) ) {
if ( $fwsetting . key -eq 'Direction' ) {
$key = 'dir'
} elseif ( $fwsetting . key -eq 'Rule Name' ) {
$key = 'name'
} elseif ( $fwsetting . key -eq 'Enabled' ) {
$key = 'enable'
} elseif ( $fwsetting . key -eq 'Profiles' ) {
$key = 'profile'
} else {
$key = $ ( $fwsetting . key ) . ToLower ( )
} ;
$execString + = " " ;
$execString + = $key ;
$execString + = " = " ;
$execString + = '"' ;
$execString + = $fwsetting . value ;
$execString + = '"' ;
} ;
try {
#$msg+=@($execString);
$output = $ ( Invoke-Expression $execString | ? { $_ } ) ;
$msg + = @ ( " Created firewall rule $name " ) ;
$result = @ {
failed = $false
output = $output
changed = $true
msg = $msg
} ;
if ( $fwsetting . value -ne $null ) {
switch ( $fwsetting . key ) {
" Direction " { $option = " dir " }
" Rule Name " { $option = " name " }
" Enabled " { $option = " enable " }
" Profiles " { $option = " profile " }
" InterfaceTypes " { $option = " interfacetype " }
" Security " { $option = " security " }
" Edge traversal " { $option = " edge " }
default { $option = $ ( $fwsetting . key ) . ToLower ( ) }
}
$command + = " $option =' $( $fwsetting . value ) ' "
}
}
try {
$rc = 0
if ( -not $check_mode ) {
$result . output = Invoke-Expression $command | Where { $_ }
$rc = $LASTEXITCODE
}
if ( $rc -eq 0 ) {
if ( $diff_support ) {
$result . diff = @ { }
$result . diff . after = ConvertTo-SortedKV $fwsettings
$result . diff . before = " "
}
$result . changed = $true
$result . msg + = @ ( " Created firewall rule ' $name ' " )
} else {
$result . failed = $true
$result . msg + = @ ( " Create command ' $command ' failed with rc= $rc " )
}
} catch [ Exception ] {
$msg = @ ( " Failed to create the rule " )
$result = @ {
output = $output
failed = $true
error = $_ . Exception . Message
msg = $msg
} ;
} ;
$result . error = $_ . Exception . Message
$result . failed = $true
$result . msg = @ ( " Failed to create the rule ' $name ' " )
}
return $result
} ;
}
function removeFireWallRule ( $fwsettings ) {
$msg = @ ( )
$result = @ {
changed = $false
failed = $false
msg = @ ( )
}
$command = " netsh advfirewall firewall delete rule name=' $( $fwsettings . 'Rule Name' ) ' "
try {
$rawoutput = @ ( netsh advfirewall firewall delete rule name = " $( $fwsettings . 'Rule Name' ) " )
$rawoutput | Where { $_ -match '^([^:]+):\s*(\S.*)$' } | Foreach -Begin {
$FirstRun = $true ;
$HashProps = @ { } ;
$rc = 0
if ( -not $check_mode ) {
$result . output = Invoke-Expression $command | Where { $_ }
$rc = $LASTEXITCODE
$result . output | Where { $_ -match '^([^:]+):\s*(\S.*)$' } | Foreach -Begin {
$FirstRun = $true
$HashProps = @ { }
} -Process {
if ( ( $Matches [ 1 ] -eq 'Rule Name' ) -and ( ! ( $FirstRun ) ) ) {
$output = $HashProps ;
$HashProps = @ { } ;
} ;
$HashProps . $ ( $Matches [ 1 ] ) = $Matches [ 2 ] ;
$FirstRun = $false ;
if ( ( $Matches [ 1 ] -eq 'Rule Name' ) -and ( -not $FirstRun ) ) {
$ result. output = $HashProps
$HashProps = @ { }
}
$HashProps . $ ( $Matches [ 1 ] ) = $Matches [ 2 ]
$FirstRun = $false
} -End {
$output = $HashProps ;
} ;
$msg + = @ ( " Removed the rule " )
$result = @ {
failed = $false
changed = $true
msg = $msg
output = $output
} ;
$result . output = $HashProps
}
}
if ( $rc -eq 0 -or $rc -eq 1 ) {
if ( $diff_support ) {
$result . diff = @ { }
$result . diff . after = " "
$result . diff . before = ConvertTo-SortedKV $fwsettings
}
$result . changed = $true
$result . msg + = @ ( " Removed the rule ' $name ' " )
} else {
$result . failed = $true
$result . msg + = @ ( " Remove command ' $command ' failed with rc= $rc " )
}
} catch [ Exception ] {
$msg + = @ ( " Failed to remove the rule " )
$result = @ {
failed = $true
error = $_ . Exception . Message
msg = $msg
$result . error = $_ . Exception . Message
$result . failed = $true
$result . msg + = @ ( " Failed to remove the rule ' $name ' " )
}
} ;
return $result
}
# Mount Drives
$change = $false ;
$fail = $false ;
$msg = @ ( ) ;
$fwsettings = @ { }
# FIXME: Unsupported keys
#$unsupported = @("Grouping", "Rule source")
$unsupported = @ ( " Rule source " )
# Variabelise the arguments
$params = Parse-Args $args ;
$result = @ {
changed = $false
fwsettings = @ { }
msg = @ ( )
}
$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
$name = Get-AnsibleParam -obj $params -name " name " -failifempty $true
$direction = Get-AnsibleParam -obj $params -name " direction " -failifempty $true -validateSet " in " , " out "
$action = Get-AnsibleParam -obj $params -name " action " -failifempty $true -validateSet " allow " , " block " , " bypass "
$program = Get-AnsibleParam -obj $params -name " program "
$service = Get-AnsibleParam -obj $params -name " service " -default " any "
$description = Get-AnsibleParam -obj $params -name " description "
$enable = ConvertTo-Bool ( Get-AnsibleParam -obj $params -name " enable " -default " true " )
$winprofile = Get-AnsibleParam -obj $params -name " profile " -default " any "
$localip = Get-AnsibleParam -obj $params -name " localip " -default " any "
$remoteip = Get-AnsibleParam -obj $params -name " remoteip " -default " any "
$localport = Get-AnsibleParam -obj $params -name " localport " -default " any "
$remoteport = Get-AnsibleParam -obj $params -name " remoteport " -default " any "
$protocol = Get-AnsibleParam -obj $params -name " protocol " -default " any "
$state = Get-AnsibleParam -obj $params -name " state " -failifempty $true -validateSet " present " , " absent "
$force = ConvertTo-Bool ( Get-AnsibleParam -obj $params -name " force " -default " false " )
$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 "
$profiles = Get-AnsibleParam -obj $params -name " profiles " -type " str " -default " domain,private,public " -aliases " profile "
$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 "
$edge = Get-AnsibleParam -obj $params -name " edge " -type " str " -default " no " -validateset " no " , " yes " , " deferapp " , " deferuser "
$interfacetypes = Get-AnsibleParam -obj $params -name " interfacetypes " -type " str " -default " any "
$security = Get-AnsibleParam -obj $params -name " security " -type " str " -default " notrequired "
$state = Get-AnsibleParam -obj $params -name " state " -type " str " -default " present " -validateset " present " , " absent "
$force = Get-AnsibleParam -obj $params -name " force " -type " bool " -default $false
# Check the arguments
If ( $enable -eq $true ) {
$fwsettings . Add ( " Enabled " , " yes " ) ;
} Else {
$fwsettings . Add ( " Enabled " , " no " ) ;
} ;
$fwsettings . Add ( " Rule Name " , $name )
#$fwsettings.Add("displayname", $name)
$state = $state . ToString ( ) . ToLower ( )
If ( $state -eq " present " ) {
$fwsettings . Add ( " Direction " , $direction )
$fwsettings . Add ( " Action " , $action )
} ;
If ( $description ) {
$fwsettings . Add ( " Description " , $description ) ;
if ( $enabled ) {
$result . fwsettings . Add ( " Enabled " , " Yes " )
} else {
$result . fwsettings . Add ( " Enabled " , " No " )
}
$result . fwsettings . Add ( " Rule Name " , $name )
#$result.fwsettings.Add("displayname", $name)
if ( $state -eq " present " ) {
$result . fwsettings . Add ( " Direction " , $ ( ConvertTo-TitleCase ( $direction ) ) )
$result . fwsettings . Add ( " Action " , $ ( ConvertTo-TitleCase $action ) )
}
if ( $description -ne $null ) {
$result . fwsettings . Add ( " Description " , $description )
}
if ( $program -ne $null ) {
$result . fwsettings . Add ( " Program " , $program )
}
If ( $program ) {
$fwsettings . Add ( " Program " , $program ) ;
$result . fwsettings . Add ( " LocalIP " , $localip )
$result . fwsettings . Add ( " RemoteIP " , $remoteip )
if ( $localport -ne $null ) {
$result . fwsettings . Add ( " LocalPort " , $localport )
}
if ( $remoteport -ne $null ) {
$result . fwsettings . Add ( " RemotePort " , $remoteport )
}
if ( $service -ne $null ) {
$result . fwsettings . Add ( " Service " , $ ( ConvertTo-TitleCase ( $service ) ) )
}
if ( $protocol -eq " Any " ) {
$result . fwsettings . Add ( " Protocol " , $protocol )
} else {
$result . fwsettings . Add ( " Protocol " , $protocol . toupper ( ) )
}
$fwsettings . Add ( " LocalIP " , $localip ) ;
$fwsettings . Add ( " RemoteIP " , $remoteip ) ;
$fwsettings . Add ( " LocalPort " , $localport ) ;
$fwsettings . Add ( " RemotePort " , $remoteport ) ;
$fwsettings . Add ( " Service " , $service ) ;
$fwsettings . Add ( " Protocol " , $protocol ) ;
$fwsettings . Add ( " Profiles " , $winprofile )
$output = @ ( )
$capture = getFirewallRule ( $fwsettings ) ;
if ( $capture . failed -eq $true ) {
$msg + = $capture . msg ;
$result = New-Object psobject @ {
changed = $false
failed = $true
error = $capture . error
msg = $msg
} ;
Exit-Json $result ;
if ( $profiles -eq " Any " ) {
$result . fwsettings . Add ( " Profiles " , " Domain,Private,Public " )
} else {
$diff = $capture . difference
$msg + = $capture . msg ;
$identical = $capture . identical ;
$multiple = $capture . multiple ;
$result . fwsettings . Add ( " Profiles " , $ ( ConvertTo-TitleCase ( $profiles ) ) )
}
$result . fwsettings . Add ( " Edge traversal " , $ ( ConvertTo-TitleCase ( $edge ) ) )
if ( $interfacetypes -ne $null ) {
$result . fwsettings . Add ( " InterfaceTypes " , $ ( ConvertTo-TitleCase ( $interfacetypes ) ) )
}
switch ( $security ) {
" Authenticate " { $security = " Authenticate " }
" AuthDynEnc " { $security = " AuthDynEnc " }
" AuthEnc " { $security = " AuthEnc " }
" AuthNoEncap " { $security = " AuthNoEncap " }
" NotRequired " { $security = " NotRequired " }
}
$result . fwsettings . Add ( " Security " , $security )
# FIXME: Define unsupported options
#$result.fwsettings.Add("Grouping", "")
#$result.fwsettings.Add("Rule source", "Local Setting")
$get = getFirewallRule ( $result . fwsettings )
$result . msg + = $get . msg
switch ( $state ) {
" present " {
if ( $capture . exists -eq $false ) {
$capture = createFireWallRule ( $fwsettings ) ;
$msg + = $capture . msg ;
$change = $true ;
if ( $capture . failed -eq $true ) {
$result = New-Object psobject @ {
failed = $capture . failed
error = $capture . error
output = $capture . output
changed = $change
msg = $msg
difference = $diff
fwsettings = $fwsettings
} ;
Exit-Json $result ;
}
} elseif ( $capture . identical -eq $false ) {
if ( $force -eq $true ) {
$capture = removeFirewallRule ( $fwsettings ) ;
$msg + = $capture . msg ;
$change = $true ;
if ( $capture . failed -eq $true ) {
$result = New-Object psobject @ {
failed = $capture . failed
error = $capture . error
changed = $change
msg = $msg
output = $capture . output
fwsettings = $fwsettings
} ;
Exit-Json $result ;
}
$capture = createFireWallRule ( $fwsettings ) ;
$msg + = $capture . msg ;
$change = $true ;
if ( $capture . failed -eq $true ) {
$result = New-Object psobject @ {
failed = $capture . failed
error = $capture . error
changed = $change
msg = $msg
difference = $diff
fwsettings = $fwsettings
} ;
Exit-Json $result ;
if ( $get . failed ) {
$result . error = $get . error
$result . output = $get . output
Fail-Json $result $result . msg
}
$result . diff = $get . diff
if ( $state -eq " present " ) {
if ( -not $get . exists ) {
$create = createFireWallRule ( $result . fwsettings )
$result . msg + = $create . msg
$result . diff = $create . diff
if ( $create . failed ) {
$result . error = $create . error
$result . output = $create . output
Fail-Json $result $result . msg
}
$result . changed = $true
} elseif ( -not $get . identical ) {
# FIXME: This ought to use netsh advfirewall firewall set instead !
if ( $force ) {
$remove = removeFirewallRule ( $result . fwsettings )
# NOTE: We retain the diff output from $get.diff here
$result . msg + = $remove . msg
if ( $remove . failed ) {
$result . error = $remove . error
$result . output = $remove . output
Fail-Json $result $result . msg
}
$create = createFireWallRule ( $result . fwsettings )
# NOTE: We retain the diff output from $get.diff here
$result . msg + = $create . msg
if ( $create . failed ) {
$result . error = $create . error
$result . output = $create . output
Fail-Json $result $result . msg
}
$result . changed = $true
} else {
$fail = $true
$msg + = @ ( " There was already a rule $name with different values, use force=True to overwrite it " ) ;
}
} elseif ( $capture . identical -eq $true ) {
$msg + = @ ( " Firewall rule $name was already created " ) ;
} ;
}
" absent " {
if ( $capture . exists -eq $true ) {
$capture = removeFirewallRule ( $fwsettings ) ;
$msg + = $capture . msg ;
$change = $true ;
if ( $capture . failed -eq $true ) {
$result = New-Object psobject @ {
failed = $capture . failed
error = $capture . error
changed = $change
msg = $msg
output = $capture . output
fwsettings = $fwsettings
} ;
Exit-Json $result ;
$result . msg + = @ ( " There was already a rule ' $name ' with different values, use the 'force' parameter to overwrite it " )
Fail-Json $result $result . msg
}
} else {
$msg + = @ ( " Firewall rule $name did not exist " ) ;
} ;
$result . msg + = @ ( " Firewall rule ' $name ' was already created " )
}
} ;
} elseif ( $state -eq " absent " ) {
$result = New-Object psobject @ {
failed = $fail
changed = $change
msg = $msg
difference = $diff
fwsettings = $fwsettings
} ;
if ( $get . exists ) {
$remove = removeFirewallRule ( $result . fwsettings )
$result . diff = $remove . diff
$result . msg + = $remove . msg
if ( $remove . failed ) {
$result . error = $remove . error
$result . output = $remove . output
Fail-Json $result $result . msg
}
$result . changed = $true
} else {
$result . msg + = @ ( " Firewall rule ' $name ' did not exist " )
}
}
Exit-Json $result ;
Exit-Json $result