DSCResources/MSFT_xWindowsFeature/MSFT_xWindowsFeature.psm1

data LocalizedData
{
    # culture="en-US"
    ConvertFrom-StringData @'
SetTargetResourceInstallwhatIfMessage=Trying to install feature {0}
SetTargetResourceUnInstallwhatIfMessage=Trying to Uninstall feature {0}
FeatureNotFoundError=The requested feature {0} is not found on the target machine.
FeatureDiscoveryFailureError=Failure to get the requested feature {0} information from the target machine. Wildcard pattern is not supported in the feature name.
FeatureInstallationFailureError=Failure to successfully install the feature {0} .
FeatureUnInstallationFailureError=Failure to successfully Unintstall the feature {0} .
QueryFeature=Querying for feature {0} using Server Manager cmdlet Get-WindowsFeature.
InstallFeature=Trying to install feature {0} using Server Manager cmdlet Add-WindowsFeature.
UninstallFeature=Trying to Uninstall feature {0} using Server Manager cmdlet Remove-WindowsFeature.
RestartNeeded=The Target machine needs to be restarted.
GetTargetResourceStartVerboseMessage=Begin executing Get functionality on the {0} feature.
GetTargetResourceEndVerboseMessage=End executing Get functionality on the {0} feature.
SetTargetResourceStartVerboseMessage=Begin executing Set functionality on the {0} feature.
SetTargetResourceEndVerboseMessage=End executing Set functionality on the {0} feature.
TestTargetResourceStartVerboseMessage=Begin executing Test functionality on the {0} feature.
TestTargetResourceEndVerboseMessage=End executing Test functionality on the {0} feature.
ServerManagerModuleNotFoundDebugMessage=ServerManager module is not installed on the machine.
SkuNotSupported=Installing roles and features using PowerShell Desired State Configuration is supported only on Server SKU's. It is not supported on Client SKU.
SourcePropertyNotSupportedDebugMessage=Source property in MSFT_RoleResource is not supported on this operating system and it was ignored.
EnableServerManagerPSHCmdletsFeature=Windows Server 2008R2 Core operating system detected: ServerManager-PSH-Cmdlets feature has been enabled.
UninstallSuccess=Successfully uninstalled the feature {0}.
InstallSuccess=Successfully installed the feature {0}.
'@

}

# Commented-out until more languages are supported
# Import-LocalizedData LocalizedData -filename MSFT_xWindowsFeature.strings.psd1

# The Get-TargetResource cmdlet is used to fetch the status of role or feature on the target machine.
# It gives the feature info of the requested role/feature on the target machine.
function Get-TargetResource
{
     [OutputType([Hashtable])]
     param
     (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,
        
        [System.Management.Automation.PSCredential]
        $Credential
     )

        $getTargetResourceResult = $null;

        $getTargetResourceStartVerboseMessage = $($LocalizedData.GetTargetResourceStartVerboseMessage) -f ${Name} ;
        Write-Debug -Message $getTargetResourceStartVerboseMessage;

        ValidatePrerequisites ;

        $qyeryFeatureMessage = $($LocalizedData.QueryFeature) -f ${Name} ;
        Write-Debug -Message $qyeryFeatureMessage;

        $isR2Sp1 = IsWinServer2008R2SP1;
        if($isR2Sp1 -and $psboundparameters.ContainsKey("Credential"))
        {
            $parameters = $psboundparameters.Remove("Credential");
            $feature = Invoke-Command -ScriptBlock { Get-WindowsFeature @using:psboundparameters } -ComputerName . -Credential $Credential -ErrorVariable ev
            $psboundparameters.Add("Credential", $Credential);
        }
        else
        {
            $feature = Get-WindowsFeature @psboundparameters -ErrorVariable ev
        }

        if($null -eq $ev -or $ev.Count -eq 0)
        {
            $foundError = $false;

            ValidateFeature $feature $Name;

            # If a feature does not contain SubFeature then $includeAllSubFeature would be set to $false.
            # If a feature contains one or more subfeatures then $includeAllSubFeature would be set to
            # $true only if all the subfeatures are installed, or else $includeAllSubFeature would be set to $false.
            $includeAllSubFeature = $true;

            if($feature.SubFeatures.Count -eq 0)
            {
                $includeAllSubFeature = $false;
            }
            else
            {
                foreach($currentSubFeature in $feature.SubFeatures)
                {
                   if($foundError -eq $false)
                   {
                        $parameters = $psboundparameters.Remove("Name");
                        $psboundparameters.Add("Name", $currentSubFeature);

                        $isR2Sp1 = IsWinServer2008R2SP1;
                        if($isR2Sp1 -and $psboundparameters.ContainsKey("Credential"))
                        {
                            $parameters = $psboundparameters.Remove("Credential");
                            $subFeature = Invoke-Command -ScriptBlock { Get-WindowsFeature @using:psboundparameters } -ComputerName . -Credential $Credential -ErrorVariable errorVar
                            $psboundparameters.Add("Credential", $Credential);
                        }
                        else
                        {
                            $subFeature = Get-WindowsFeature @psboundparameters -ErrorVariable errorVar
                        }

                        if($null -eq $errorVar -or $errorVar.Count -eq 0)
                        {
                            ValidateFeature $subFeature $currentSubFeature;

                            if(!$subFeature.Installed)
                            {
                                $includeAllSubFeature = $false;
                                break;
                            }
                        }
                        else
                        {
                            $foundError = $true;;
                        }
                    }
                }
            }

            if($foundError -eq $false)
            {
                if($feature.Installed)
                {
                    $ensureResult = "Present";
                }
                else
                {
                    $ensureResult = "Absent";
                }

                # Add all feature properties to the hash table
                $getTargetResourceResult = @{
                                                Name = $feature.Name;
                                                DisplayName = $feature.DisplayName;
                                                Ensure = $ensureResult;
                                                IncludeAllSubFeature = $includeAllSubFeature;
                                            }

                $getTargetResourceEndVerboseMessage = $($LocalizedData.GetTargetResourceEndVerboseMessage) -f ${Name} ;
                Write-Debug -Message $getTargetResourceEndVerboseMessage;

                $getTargetResourceResult;
            }
        }
}


# The Set-TargetResource cmdlet is used to install or uninstall a role on the target machine.
# It also supports installing & uninstalling the role or feature specific subfeatures.
function Set-TargetResource
{
    [CmdletBinding(SupportsShouldProcess=$true, DefaultParameterSetName = "FeatureName")]

     param
     (
        [parameter(Mandatory=$true, ParameterSetName = "FeatureName")]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [parameter()]
        [ValidateSet("Present", "Absent")]
        [string]
        $Ensure = "Present",

        [parameter()]
        [ValidateNotNullOrEmpty()]
        [string]
        $Source,

        [System.Boolean]
        $IncludeAllSubFeature = $false,

        [Parameter(Mandatory=$false)]
        [System.Management.Automation.PSCredential]
        $Credential,

        [ValidateNotNullOrEmpty()]
        [string]
        $LogPath
     )

    $getTargetResourceResult = $null;

    $inputParameter = $null;

    $setTargetResourceStartVerboseMessage = $($LocalizedData.SetTargetResourceStartVerboseMessage) -f ${Name} ;
    Write-Debug -Message $setTargetResourceStartVerboseMessage;

    ValidatePrerequisites ;

    # -Source Parameter is not applicable to Windows Server 2008 R2 SP1. Hence removing it.
    # all role/feature spcific binaries are avaliable inboc on Windows Server 2008 R2 SP1, hence
    # -Source is not supported on Windows Server 2008 R2 SP1.
    $isR2Sp1 = IsWinServer2008R2SP1;
    if($isR2Sp1 -and $psboundparameters.ContainsKey("Source"))
    {
        $sourcePropertyNotSupportedDebugMessage = $($LocalizedData.SourcePropertyNotSupportedDebugMessage) ;
        Write-Debug -Message $sourcePropertyNotSupportedDebugMessage;

        $parameters = $psboundparameters.Remove("Source");
    }


    if($Ensure -eq "Present")
    {
        $parameters = $psboundparameters.Remove("Ensure");

        $installFeatureMessage = $($LocalizedData.InstallFeature) -f ${Name} ;
        Write-Debug -Message $installFeatureMessage;

        $isR2Sp1 = IsWinServer2008R2SP1;
        if($isR2Sp1 -and $psboundparameters.ContainsKey("Credential"))
        {
            $parameters = $psboundparameters.Remove("Credential");
            $feature = Invoke-Command -ScriptBlock { Add-WindowsFeature @using:psboundparameters } -ComputerName . -Credential $Credential -ErrorVariable ev
            $psboundparameters.Add("Credential", $Credential);
        }
        else
        {
            $feature = Add-WindowsFeature @psboundparameters -ErrorVariable ev
        }

        if($feature -ne $null -and $feature.Success -eq $true)
        {
            Write-Verbose ($LocalizedData.InstallSuccess -f $Name)

            # Check if reboot is required, if so notify CA.
            if($feature.RestartNeeded -eq "Yes")
            {
                $restartNeededMessage = $($LocalizedData.RestartNeeded);
                Write-Verbose -Message $restartNeededMessage;

                $global:DSCMachineStatus = 1;
            }
        }
        else
        {
            # Add-WindowsFeature cmdlet falied to successfully install the requested feature.
            # If there are errors from the Add-WindowsFeature cmdlet. We surface those errors.
            # If Add-WindwosFeature cmdlet does not surface any errors. Then the provider throws a
            # terminating error.
            if($null -eq $ev -or $ev.Count -eq 0)
            {
                $errorId = "FeatureInstallationFailure";
                $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation;
                $errorMessage = $($LocalizedData.FeatureInstallationFailureError) -f ${Name} ;
                $exception = New-Object System.InvalidOperationException $errorMessage ;
                $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null

                $PSCmdlet.ThrowTerminatingError($errorRecord);
            }
        }
    }
    else
    {
        $parameters = $psboundparameters.Remove("Ensure");
        $parameters = $psboundparameters.Remove("IncludeAllSubFeature");
        $parameters = $psboundparameters.Remove("Source");

        $uninstallFeatureMessage = $($LocalizedData.UninstallFeature) -f ${Name} ;
        Write-Debug -Message $uninstallFeatureMessage;

        $isR2Sp1 = IsWinServer2008R2SP1;
        if($isR2Sp1 -and $psboundparameters.ContainsKey("Credential"))
        {
            $parameters = $psboundparameters.Remove("Credential");
            $feature = Invoke-Command -ScriptBlock { Remove-WindowsFeature @using:psboundparameters } -ComputerName . -Credential $Credential -ErrorVariable ev
            $psboundparameters.Add("Credential", $Credential);
        }
        else
        {
            $feature = Remove-WindowsFeature @psboundparameters -ErrorVariable ev
        }

        if($feature -ne $null -and $feature.Success -eq $true)
        {
            Write-Verbose ($LocalizedData.UninstallSuccess -f $Name)

            # Check if reboot is required, if so notify CA.
            if($feature.RestartNeeded -eq "Yes")
            {
                $restartNeededMessage = $($LocalizedData.RestartNeeded);
                Write-Verbose -Message $restartNeededMessage;

                $global:DSCMachineStatus = 1;
            }
        }
        else
        {
            # Remove-WindowsFeature cmdlet falied to successfully Uninstall the requested feature.
            # If there are errors from the Remove-WindowsFeature cmdlet. We surface those errors.
            # If Remove-WindwosFeature cmdlet does not surface any errors. Then the provider throws a
            # terminating error.
            if($null -eq $ev -or $ev.Count -eq 0)
            {
                $errorId = "FeatureUnInstallationFailure";
                $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation;
                $errorMessage = $($LocalizedData.FeatureUnInstallationFailureError) -f ${Name} ;
                $exception = New-Object System.InvalidOperationException $errorMessage ;
                $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null

                $PSCmdlet.ThrowTerminatingError($errorRecord);
            }
        }
    }

    $setTargetResourceEndVerboseMessage = $($LocalizedData.SetTargetResourceEndVerboseMessage) -f ${Name} ;
    Write-Debug -Message $setTargetResourceEndVerboseMessage;
}

# The Test-TargetResource cmdlet is used to validate if the role or feature is in a state as expected in the instance document.
function Test-TargetResource
{
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [parameter()]
        [ValidateSet("Present", "Absent")]
        [string]
        $Ensure = "Present",

        [parameter()]
        [ValidateNotNullOrEmpty()]
        [string]
        $Source,

        [System.Boolean]
        $IncludeAllSubFeature = $false,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $Credential,

        [ValidateNotNullOrEmpty()]
        [string]
        $LogPath

    )

        $testTargetResourceStartVerboseMessage = $($LocalizedData.TestTargetResourceStartVerboseMessage) -f ${Name} ;
        Write-Debug -Message $testTargetResourceStartVerboseMessage;

        ValidatePrerequisites ;

        $testTargetResourceResult = $false;

        $parameters = $psboundparameters.Remove("Ensure");
        $parameters = $psboundparameters.Remove("IncludeAllSubFeature");
        $parameters = $psboundparameters.Remove("Source");

        $qyeryFeatureMessage = $($LocalizedData.QueryFeature) -f ${Name} ;
        Write-Debug -Message $qyeryFeatureMessage;

        $isR2Sp1 = IsWinServer2008R2SP1;
        if($isR2Sp1 -and $psboundparameters.ContainsKey("Credential"))
        {
            $parameters = $psboundparameters.Remove("Credential");
            $feature = Invoke-Command -ScriptBlock { Get-WindowsFeature @using:psboundparameters } -ComputerName . -Credential $Credential -ErrorVariable ev
            $psboundparameters.Add("Credential", $Credential);
        }
        else
        {
            $feature = Get-WindowsFeature @psboundparameters -ErrorVariable ev
        }


        if($null -eq $ev -or $ev.Count -eq 0)
        {
            ValidateFeature $feature $Name;


            # Check if the feature is in the requested Ensure state.
            # If so then check if then check if the subfeature is in the requested Ensure state.
            if(($Ensure -eq "Present" -and $feature.Installed -eq $true) -or
                ($Ensure -eq "Absent" -and $feature.Installed -eq $false))
            {
                $testTargetResourceResult = $true;

                # IncludeAllSubFeature is set to $true, so we need to make
                # sure that all Sub Features are alsi installed.
                if($IncludeAllSubFeature)
                {

                    foreach($currentSubFeature in $feature.SubFeatures)
                    {
                        $parameters = $psboundparameters.Remove("Name");
                        $parameters = $psboundparameters.Remove("Ensure");
                        $parameters = $psboundparameters.Remove("IncludeAllSubFeature");
                        $parameters = $psboundparameters.Remove("Source");
                        $psboundparameters.Add("Name", $currentSubFeature);

                        $isR2Sp1 = IsWinServer2008R2SP1;
                        if($isR2Sp1 -and $psboundparameters.ContainsKey("Credential"))
                        {
                            $parameters = $psboundparameters.Remove("Credential");
                            $subFeature = Invoke-Command -ScriptBlock { Get-WindowsFeature @using:psboundparameters } -ComputerName . -Credential $Credential -ErrorVariable errorVar
                            $psboundparameters.Add("Credential", $Credential);
                        }
                        else
                        {
                            $subFeature = Get-WindowsFeature @psboundparameters -ErrorVariable errorVar
                        }


                        if($null -eq $errorVar -or $errorVar.Count -eq 0)
                        {
                            ValidateFeature $subFeature $currentSubFeature;

                            if(!$subFeature.Installed)
                            {
                                $testTargetResourceResult = $false;
                                break;
                            }
                        }
                    }
                }
            }

            $testTargetResourceEndVerboseMessage = $($LocalizedData.TestTargetResourceEndVerboseMessage) -f ${Name} ;
            Write-Debug -Message $testTargetResourceEndVerboseMessage;

            $testTargetResourceResult;
        }
}


# ValidateFeature is a helper function used to validate the results of SM+ cmdlets
# Get-Windowsfeature for the user supplied feature name.
function ValidateFeature
{
    param
    (
        [object] $feature,

        [string] $Name
    )

    if($null -eq $feature)
    {
        $errorId = "FeatureNotFound";
        $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation
        $errorMessage = $($LocalizedData.FeatureNotFoundError) -f ${Name}
        $exception = New-Object System.InvalidOperationException $errorMessage
        $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null

        $PSCmdlet.ThrowTerminatingError($errorRecord);
    }

    # WildCard pattern is not supported by the role provider.
    # Hence we restrict user to request only one feature information in a single request.
    if($feature.Count -gt 1)
    {
        $errorId = "FeatureDiscoveryFailure";
        $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult
        $errorMessage = $($LocalizedData.FeatureDiscoveryFailureError) -f ${Name}
        $exception = New-Object System.InvalidOperationException $errorMessage
        $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null

        $PSCmdlet.ThrowTerminatingError($errorRecord);
    }
}

# ValidatePrerequisites is a helper function used to validate if the MSFT_RoleResource is supported on the target machine.
# MSFT_RoleResource is supported only on Server SKU's. MSFT_RoleResource depend on ServerManagerModule which is avaliable
# only on Server SKU's.
function ValidatePrerequisites
{
    param
    (
    )

    #Enable ServerManager-PSH-Cmdlets feature if os is WS2008R2 Core.
    $datacenterServerCore = 12
    $standardServerCore = 13
    $EnterpriseServerCore = 14

    $operatingSystem = Get-WmiObject -Class Win32_operatingsystem
    if($operatingSystem.Version.StartsWith('6.1.') -and
        (($operatingSystem.OperatingSystemSKU -eq $datacenterServerCore) -or ($operatingSystem.OperatingSystemSKU -eq $standardServerCore) -or ($operatingSystem.OperatingSystemSKU -eq $EnterpriseServerCore)))
    {
        Write-Verbose -Message $($LocalizedData.EnableServerManagerPSHCmdletsFeature)

        # Update:ServerManager-PSH-Cmdlets has a depndency on Powershell 2 update: MicrosoftWindowsPowerShell
        # Hence enabling MicrosoftWindowsPowerShell.
        dism /online /enable-feature /FeatureName:MicrosoftWindowsPowerShell | Out-Null
        dism /online /enable-feature /FeatureName:ServerManager-PSH-Cmdlets | Out-Null
    }

    if(Import-Module ServerManager -PassThru -Verbose:$false -ErrorAction Ignore)
    {
    }
    else
    {

        $serverManagerModuleNotFoundDebugMessage = $($LocalizedData.ServerManagerModuleNotFoundDebugMessage);
        Write-Debug -Message $serverManagerModuleNotFoundDebugMessage;

        $errorId = "SkuNotSupported";
        $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation
        $errorMessage = $($LocalizedData.SkuNotSupported)
        $exception = New-Object System.InvalidOperationException $errorMessage
        $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null

        $PSCmdlet.ThrowTerminatingError($errorRecord);
    }
}


# IsWinServer2008R2SP1 is a helper function to detect if the target machine is a Win 2008 R2 SP1.
function IsWinServer2008R2SP1
{
    param
    (
    )

    # We are already checking for the Presence of ServerManager module before using this helper function.
    # Hence checking for the version shoudl be good enough to confirm that the target machine is
    # Windows Server 2008 R2 machine.
    if([Environment]::OSVersion.Version.ToString().Contains("6.1."))
    {
        return $true;
    }

    return $false;
}

Export-ModuleMember -function Get-TargetResource, Set-TargetResource, Test-TargetResource