PowerShell DSC: cLegacyFirewall Helper Functions

  |   Source

Almost done with my cLegacyFirewall Desired State Configuration (DSC) Resource. There are a few other helper functions required to make this Resource work correctly, and they're primarily modified versions of those supplied in the PowerShell DSC Waves 'xNetworking' - 'xFirewall' Resource. I will also touch on the schema.mof and Module Manifest files.

First up is Test-RuleHasProperties. This function validates firewall rules that exist on the target machine against that called in the Resource; for example from this test, DSC will know whether the 'Set-TargetResource' function is required.

# Function to validate if the supplied Rule adheres to all parameters set
Function Test-RuleHasProperties
    {
        param
        (
            [Parameter(Mandatory)]
            $FirewallRule,

            [String]
            $DisplayName,

            [String]$Enabled,

            [String]
            $Direction,

            $Profiles,

            $LocalIP,

            $RemoteIP,

            [String]
            $Program,
            $LocalPort,

            $RemotePort,

            $Protocol,

            [String]
            $Action
        )

        $desiredConfigurationMatch = $true

        if ($DisplayName -and ($FirewallRule.'Rule Name' -ne $DisplayName))
        {
            Write-Verbose "Function: Test-RuleHasProperties: DisplayName property value - $FirewallRule.'Rule Name' does not match desired state - $DisplayName"

            $desiredConfigurationMatch = $false
        }

        if ($Enabled -and ($FirewallRule.Enabled -ne $Enabled))
        {
            Write-Verbose "Function: Test-RuleHasProperties: Enabled property value - $($FirewallRule.Enabled) does not match desired state - $Enabled"

            $desiredConfigurationMatch = $false
        }

        if ($Direction -and ($FirewallRule.Direction -ne $Direction))
        {
            Write-Verbose "Function: Test-RuleHasProperties: Direction property value - $($FirewallRule.Direction) does not match desired state - $Direction"

            $desiredConfigurationMatch = $false
        }

        if ($Profiles -eq "Any")
        {
            if ($Profiles -and ($FirewallRule.Profiles -ne "Domain,Private,Public"))
            {
                Write-Verbose "Function: Test-RuleHasProperties: Profiles property value - $($FirewallRule.Profiles) does not match desired state - $Profiles"

                $desiredConfigurationMatch = $false
            }
        }
        elseif ($Profiles -and ($FirewallRule.Profiles -ne $Profiles))
        {
            Write-Verbose "Function: Test-RuleHasProperties: Profiles property value - $($FirewallRule.Profiles) does not match desired state - $Profiles"

            $desiredConfigurationMatch = $false
        }

        if ($LocalIP -and ($FirewallRule.LocalIP -ne $LocalIP))
        {
            Write-Verbose "Function: Test-RuleHasProperties: LocalIP property value - $($FirewallRule.LocalIP) does not match desired state - $LocalIP"

            $desiredConfigurationMatch = $false
        }

        if ($RemoteIP -and ($FirewallRule.RemoteIP -ne $RemoteIP))
        {
            Write-Verbose "Function: Test-RuleHasProperties: RemoteIP property value - $($FirewallRule.RemoteIP) does not match desired state - $RemoteIP"

            $desiredConfigurationMatch = $false
        }

        if ($Program -and ($FirewallRule.Program -ne $Program))
        {
            Write-Verbose "Function: Test-RuleHasProperties: Program property value - $($FirewallRule.Program) does not match desired state - $Program"

            $desiredConfigurationMatch = $false
        }

        if ($LocalPort -and ($FirewallRule.LocalPort -ne $LocalPort))
        {
            Write-Verbose "Function: Test-RuleHasProperties: Program property value - $($FirewallRule.LocalPort) does not match desired state - $LocalPort"

            $desiredConfigurationMatch = $false
        }

        if ($RemotePort -and ($FirewallRule.RemotePort -ne $RemotePort))
        {
            Write-Verbose "Function: Test-RuleHasProperties: Program property value - $($FirewallRule.RemotePort) does not match desired state - $RemotePort"

            $desiredConfigurationMatch = $false
        }

        if ($Protocol -and ($FirewallRule.Protocol -ne $Protocol))
        {
            Write-Verbose "Function: Test-RuleHasProperties: Program property value - $($FirewallRule.Protocol) does not match desired state - $Protocol"

            $desiredConfigurationMatch = $false
        }

        if ($Action -and ($FirewallRule.Action -ne $Action))
        {
            Write-Verbose "Function: Test-RuleHasProperties: Action property value - $($FirewallRule.Action) does not match desired state - $Action"

            $desiredConfigurationMatch = $false
        }

        Write-Verbose "Function: Test-RuleHasProperties returning $desiredConfigurationMatch"
        return $desiredConfigurationMatch
    }
}

As mentioned above the code, this simply test every property of the firewall rule, and if any do not match those specified in the configuration, the $desiredConfigurationMatch variable is set to $false; which is then returned back.

The only other helper function used is Add-cLegacyFirewallRule. This function builds the required 'netsh' command line to create the rule from DSC; then calls that netsh command to create the rule.

    Function Add-cLegacyFWRule {
        # Set the Firewall rule based on specified parameters

        $RunCommand = "netsh advfirewall firewall add rule"
        If ($Displayname)         {$RunCommand += " name=`"$Displayname`""}
        If ($Direction)           {$RunCommand += " dir=$Direction"}
        If ($Action)              {$RunCommand += " action=$Action"}
        If ($Enabled)             {$RunCommand += " enable=$Enabled"}
        If ($LocalIP -ne "Any")   {$RunCommand += " localip=$LocalIP"}
        If ($RemoteIP -ne "Any")  {$RunCommand += " remoteip=$RemoteIP"}
        If ($Profiles)            {$RunCommand += " profile=$Profiles"}
        If ($Program)             {$RunCommand += " program=`"$Program`""}
        If ($LocalPort)           {$RunCommand += " localport=`"$LocalPort`""}
        If ($RemotePort)          {$RunCommand += " remoteport=`"$RemotePort`""}
        If ($Protocol)            {$RunCommand += " protocol=$Protocol"}

        $retVal = Invoke-Expression -Command:$RunCommand

        return $retVal
    }

The remaining requirements for this Desired State Configuration Resource are the Module Manifest, and the schema.mof file. The layout of these is pretty straight forward.

The Module Manifest 'shell' can be created with the "New-ModuleManifest" cmdlet, this is the simplest form of the command and you can then simply modify the text file as required:

New-ModuleManifest -Path 'Path\to\DSCResource'

My manifest can be found in my Resource, which is attached at the end of this post.

The schema.mof file (in this Resource, its called cLegacyFirewall.schema.mof) outlines the DSC Resources parameters. Think of it in a similar way to the 'param' sections of the Get/Set/Test-TargetResource functions, however it is the schematic for the Resource class, eg:

[ClassVersion("1.0.0"), FriendlyName("cLegacyFirewall")]
class cLegacyFirewall : OMI_BaseResource
{
[Key, Description("Display Name of firewall rule")] string DisplayName;
[Write, Description("Enable the firewall rule"),ValueMap{"Yes", "No"},Values{"Yes", "No"}] string Enabled;
[Write, Description("Direction of the connection"),ValueMap{"In", "Out"},Values{"In", "Out"}] string Direction;
[Write, Description("Profile for firewall rule"),ValueMap{"Any","Domain","Private","Public"},Values{"Any","Domain","Private","Public"}] string Profiles;
[Write, Description("Local IP Restrictions")] string LocalIP;
[Write, Description("Remote IP Restrictions")] string RemoteIP;
[Write, Description("Program Path")] string Program;
[Write, Description("Local Port")] string LocalPort;
[Write, Description("Remote Port")] string RemotePort;
[Write, Description("Protocol")] string Protocol;
[Write, Description("Rule action"),ValueMap{"Allow", "Block", "Bypass"},Values{"Allow", "Block", "Bypass"}] string Action;
[Write,ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] string Ensure;
};

So there you go, that's the end of the posts regarding my cLegacyFirewall Desired State Configuration Resource; I hope you've found these posts useful and have learnt something about creating your own DSC Resources.

This particular resource can be found here, I haven't created a github repository to store my Resources yet, although I really should! This Resource may be making it's way into one of the PowerShell communities github repository, however I'll wait to announce that until it's happened.