DSCResources/cCMFolder/cCMFolder.psm1

######################################################################################
# The Get-TargetResource cmdlet.
# This function will get the collection if it exists and return all information
######################################################################################
function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $FolderName,
        
        [parameter(Mandatory = $true)]
        [validateset("2","7","9","14","17","18","19","20","23","25","2011","5000","5001","6000","6001")]
        [System.String]
        $FolderType,
        
        [parameter(Mandatory = $true)]
        [PSCredential]
        $SCCMAdministratorCredential
    )
    
    #Login
    ($oldToken, $context, $newToken) = ImpersonateAs -cred $SCCMAdministratorCredential

    #Load Module if missing then set the location for execution
    if(!(Get-Module ConfigurationManager)) {
        Try {
            Import-Module ($Env:SMS_ADMIN_UI_PATH.Substring(0,$Env:SMS_ADMIN_UI_PATH.Length-5) + '\ConfigurationManager.psd1')
            }
        Catch {
            Throw "Cannot load the SCCM Module, please ensure the SCCM Admin tools are installed and try again"
            }
        }
    $ComputerInfo = Get-WmiObject Win32_ComputerSystem
    $ComputerFQDN = $ComputerInfo.Name + '.' + $ComputerInfo.Domain
    $CM12ProviderLocation = Get-WmiObject -Query "Select * From SMS_ProviderLocation where ProviderForLocalSite = True" -Namespace "root\sms" -computername $ComputerFQDN
    $Site = $CM12ProviderLocation.SiteCode
    if(!((Get-PSDrive) -like $Site)) {
        throw "Problems discovering a valid Site. Please Investigate."
        }
    $OriginalLocation = Get-Location
    Set-Location ${Site}:

    #Get Folder Information
    $CurrentFolder = Get-WmiObject -Class SMS_ObjectContainerNode -Namespace Root\SMS\Site_$Site | where-object {$_.Name -eq $FolderName -and $_.ObjectType -eq $FolderType}

    #Translate ParentContainerNodeID to ParentFolder
    $CurrentParentFolderID = $CurrentFolder.ParentContainerNodeID
    if($CurrentParentFolderID -eq "0") {
        $CurrentParentFolder ="Root"
        }
    else {
        $CurrentParentFolder = (Get-WmiObject -Class SMS_ObjectContainerNode -Namespace Root\SMS\Site_$Site -filter "ContainerNodeID='$CurrentParentFolderID'").Name
        }
    
    $ReturnValue = @{
        FolderName = $CurrentFolder.Name
        FolderType = if($CurrentFolder.ObjectType -eq '2'){'Package'}
            elseif($CurrentFolder.ObjectType -eq '7'){'Query'}
            elseif($CurrentFolder.ObjectType -eq '9'){'Software Metering'}
            elseif($CurrentFolder.ObjectType -eq '14'){'Operating System Installers'}
            elseif($CurrentFolder.ObjectType -eq '17'){'State Migration'}
            elseif($CurrentFolder.ObjectType -eq '18'){'Image Package'}
            elseif($CurrentFolder.ObjectType -eq '19'){'Boot Image'}
            elseif($CurrentFolder.ObjectType -eq '20'){'Task Sequence'}
            elseif($CurrentFolder.ObjectType -eq '23'){'Driver Package'}
            elseif($CurrentFolder.ObjectType -eq '25'){'Driver'}
            elseif($CurrentFolder.ObjectType -eq '2011'){'Configuration Baseline'}
            elseif($CurrentFolder.ObjectType -eq '5000'){'Device Collection'}
            elseif($CurrentFolder.ObjectType -eq '5001'){'User Collection'}
            elseif($CurrentFolder.ObjectType -eq '6000'){'Application'}
            elseif($CurrentFolder.ObjectType -eq '6001'){'Configuration Item'}
            else{''}
        ParentFolder = $CurrentParentFolder
        Ensure = if($CurrentFolder){'Present'}else{'Absent'}
        }
    
    #Logout
    Set-Location $OriginalLocation
    if ($context) {
            $context.Undo()
            $context.Dispose()
            CloseUserToken($newToken)
        }

    $ReturnValue    
}

######################################################################################
# The Set-TargetResource cmdlet.
# This function will pass the "apply" switch back to the validate function
######################################################################################
function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $FolderName,

        [parameter(Mandatory = $true)]        
        [ValidateSet("2","7","9","14","17","18","19","20","23","25","2011","5000","5001","6000","6001")]
        [System.String]
        $FolderType,
        
        [parameter(Mandatory = $true)]
        [PSCredential]
        $SCCMAdministratorCredential,

        [System.String]
        $ParentFolder,

        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure
    )

    ValidateProperties @PSBoundParameters -Apply
}

######################################################################################
# The Test-TargetResource cmdlet.
# This function will only return a $true $false on compliance
######################################################################################
function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $FolderName,

        [parameter(Mandatory = $true)]
        [ValidateSet("2","7","9","14","17","18","19","20","23","25","2011","5000","5001","6000","6001")]
        [System.String]
        $FolderType,
        
        [parameter(Mandatory = $true)]
        [PSCredential]
        $SCCMAdministratorCredential,

        [System.String]
        $ParentFolder,

        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure
    )

    ValidateProperties @PSBoundParameters
}

######################################################################################
# The ValidateProperties cmdlet.
# This function accepts an -apply flag and "does the work"
######################################################################################
function ValidateProperties
{
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $FolderName,

        [parameter(Mandatory = $true)]
        [ValidateSet("2","7","9","14","17","18","19","20","23","25","2011","5000","5001","6000","6001")]
        [System.String]
        $FolderType,
        
        [parameter(Mandatory = $true)]
        [PSCredential]
        $SCCMAdministratorCredential,

        [System.String]
        $ParentFolder,

        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure = 'Present',

        [Switch]$Apply
    )
    #Set initial TestedOK value to true, whch will be called later to see if all variables are still valid
    [boolean]$TestedOK = $true

    #Login
    ($oldToken, $context, $newToken) = ImpersonateAs -cred $SCCMAdministratorCredential

    #Load Module if missing then set the location for execution
    if(!(Get-Module ConfigurationManager)) {
        Try {
            Import-Module ($Env:SMS_ADMIN_UI_PATH.Substring(0,$Env:SMS_ADMIN_UI_PATH.Length-5) + '\ConfigurationManager.psd1')
            }
        Catch {
            Throw "Cannot load the SCCM Module, please ensure the SCCM Admin tools are installed and try again"
            }
        }
    $ComputerInfo = Get-WmiObject Win32_ComputerSystem
    $ComputerFQDN = $ComputerInfo.Name + '.' + $ComputerInfo.Domain
    $CM12ProviderLocation = Get-WmiObject -Query "Select * From SMS_ProviderLocation where ProviderForLocalSite = True" -Namespace "root\sms" -computername $ComputerFQDN
    $Site = $CM12ProviderLocation.SiteCode
    if(!((Get-PSDrive) -like $Site)) {
        throw "Problems discovering a valid Site. Please Investigate."
        }

    $OriginalLocation = Get-Location
    Set-Location ${Site}:

    #Attempt to Get Existing Folder and it's ID
    $CurrentFolder = Get-WmiObject -Class SMS_ObjectContainerNode -Namespace Root\SMS\Site_$Site | where-object {$_.Name -eq $FolderName -and $_.ObjectType -eq $FolderType}
    $CurrentFolderID = $CurrentFolder.ContainerNodeID


    If($Ensure -eq 'Absent') {
       
        #Delete if Folder exists
        If($CurrentFolder) {
            If($Apply) {
                Try {
                    $CurrentFolder.Delete()
                    }
                Catch {
                    Throw "Failed to Delete Folder $CurrentFolder, please check to ensure it's empty."
                    }
                }
            else {
                [boolean]$TestedOK = $false
                }
            }
        }
    
    else {
        #Test if the current Parent FolderExists
        If($ParentFolder -and ($ParentFolder -ne 'Root')) {
            $CurrentParentFolder = Get-WmiObject -Class SMS_ObjectContainerNode -Namespace Root\SMS\Site_$Site | where-object {$_.Name -eq $ParentFolder -and $_.ObjectType -eq $FolderType}
            if (!($CurrentParentFolder)) {
                throw "The folder $ParentFolder cannot be found in Site $Site."
                }
            }
    
        #Grab the ParentFolderID
        if (!($ParentFolder) -or ($ParentFolder -eq 'Root')) {
            $ParentFolderID = 0
            }
        else {
            $ParentFolderID = (Get-WmiObject -Class SMS_ObjectContainerNode -Namespace Root\SMS\Site_$Site | where-object {$_.Name -eq $ParentFolder -and $_.ObjectType -eq $FolderType}).ContainerNodeID
            }

        #Create theFolder if it doesn't exist
        if(!($CurrentFolder)) {
            if($apply) {
                $Arguments = @{
                    Name = $FolderName;
                    ObjectType = $FolderType;
                    ParentContainerNodeID = $ParentFolderID
                    }
                Write-Verbose -Message "Create folder $FolderName with parent folder $ParentFolderName"
                Set-WmiInstance -Class SMS_ObjectContainerNode -Arguments $Arguments -Namespace Root\SMS\Site_$Site | Out-Null
                Start-Sleep 2
                }
            else {
                [boolean]$TestedOK = $false
                }
            }
    
        #Move the Folder if it's in the wrong location
        ##Re-gather the variable as a previous command may change values
        $CurrentFolder = Get-WmiObject -Class SMS_ObjectContainerNode -Namespace Root\SMS\Site_$Site | where-object {$_.Name -eq $FolderName -and $_.ObjectType -eq $FolderType}

        #Translate ParentContainerNodeID to ParentFolder
        $CurrentParentFolderID = $CurrentFolder.ParentContainerNodeID
        if($CurrentParentFolderID -ne $ParentFolderID) {
            if($Apply) {
                $Arguments = @{
                    Name = $FolderName;
                    ObjectType = $FolderType;
                    ParentContainerNodeID = $ParentFolderID
                    }
                Set-WmiInstance -Class SMS_ObjectContainerNode -Arguments $Arguments -Namespace Root\SMS\Site_$Site | Out-Null
                }
            else {
                [boolean]$TestedOK = $false
                }
            }
        }

    #Logout
    Set-Location $OriginalLocation
    if ($context) {
            $context.Undo()
            $context.Dispose()
            CloseUserToken($newToken)
        }

    #Return a true/false if the apply switch wasn't set
    if(!($apply)){
        return $TestedOK
        }
}


######################################################################################
# The below functions are used for user impersonation
# There are 3 functions in total
######################################################################################
function Get-ImpersonatetLib
{
    if ($script:ImpersonateLib)
    {
        return $script:ImpersonateLib
    }

    $sig = @'
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
 
[DllImport("kernel32.dll")]
public static extern Boolean CloseHandle(IntPtr hObject);
'@
 
   $script:ImpersonateLib = Add-Type -PassThru -Namespace 'Lib.Impersonation' -Name ImpersonationLib -MemberDefinition $sig 

   return $script:ImpersonateLib
    
}

function ImpersonateAs([PSCredential] $cred)
{
    [IntPtr] $userToken = [Security.Principal.WindowsIdentity]::GetCurrent().Token
    $userToken
    $ImpersonateLib = Get-ImpersonatetLib

    $bLogin = $ImpersonateLib::LogonUser($cred.GetNetworkCredential().UserName, $cred.GetNetworkCredential().Domain, $cred.GetNetworkCredential().Password, 
    9, 0, [ref]$userToken)
    
    if ($bLogin)
    {
        $Identity = New-Object Security.Principal.WindowsIdentity $userToken
        $context = $Identity.Impersonate()
    }
    else
    {
        throw "Can't Logon as User $cred.GetNetworkCredential().UserName."
    }
    $context, $userToken
}

function CloseUserToken([IntPtr] $token)
{
    $ImpersonateLib = Get-ImpersonatetLib

    $bLogin = $ImpersonateLib::CloseHandle($token)
    if (!$bLogin)
    {
        throw "Can't close token"
    }
}


Export-ModuleMember -Function *-TargetResource