DSCResources/NetscapeBookmarkFolder/NetscapeBookmarkFolder.psm1

Enum Ensure {
    Present
    Absent
}

# Import helper functions
$UtilPath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Utils'
Import-Module (Join-Path $UtilPath 'ConvertTo-HashTable.psm1') -DisableNameChecking
Import-Module (Join-Path $UtilPath 'Validate-DateTime.psm1') -DisableNameChecking

# Import parser libraries
$LibPath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Libs'
if (-not ('BookmarksManager.NetscapeBookmarksReader' -as [type])) {
    Add-Type -Path (Join-Path $LibPath '\BookmarksManager\netstandard1.6\BookmarksManager.dll')
}

function Get-TargetResource {
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        # The path to the bookmark file
        [Parameter(Mandatory = $true)]
        [System.String]
        $Path,

        # The title of the bookmark folder
        [Parameter(Mandatory = $true)]
        [System.String]
        $Title
    )

    $GetObject = @{
        Ensure = [Ensure]::Present
        Path   = $Path
        Title  = $Title
    }

    # Test if the bookmark file exists
    if ([string]::IsNullOrWhiteSpace($Path) -or (-not (Test-Path -LiteralPath $Path -PathType Leaf))) {
        Write-Verbose -Message 'The bookmark file does not exist.'
        $GetObject.Ensure = [Ensure]::Absent
        return $GetObject
    }

    # Read and parse the bookmark file
    $BookmarkContent = Get-Content -LiteralPath $Path -Raw -Encoding utf8
    $Reader = [BookmarksManager.NetscapeBookmarksReader]::new()
    try {
        $Bookmark = $Reader.Read($BookmarkContent)
    }
    catch {
        # Failed to parse the bookmark file
        Write-Warning -Message 'Failed to parse the bookmark file.'
        $GetObject.Ensure = [Ensure]::Absent
        return $GetObject
    }

    # Test if the folder exists in the bookmark file
    $Folder = $Bookmark.AllFolders | ? { $_.Title -eq $Title } | select -First 1
    if ($null -eq $Folder) {
        # Folder does not exist
        $GetObject.Ensure = [Ensure]::Absent
        return $GetObject
    }
    else {
        # Folder exists
        $GetObject.Ensure = [Ensure]::Present
    }

    # Get ADD_DATE property
    $GetObject.AddDate = $Folder.Added

    # Get LAST_MODIFIED property
    $GetObject.ModifiedDate = $Folder.LastModified

    # Get ATTRIBUTES property
    $GetObject.Attributes = $Folder.Attributes

    return $GetObject
}


function Set-TargetResource {
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.String]
        $Ensure = [Ensure]::Present,

        # Path to the bookmark file
        [Parameter(Mandatory = $true)]
        [System.String]
        $Path,

        # Title of the bookmark folder
        [Parameter(Mandatory = $true)]
        [System.String]
        $Title,

        # ADD_DATE attribute
        [Parameter()]
        [Nullable[System.DateTime]]
        $AddDate,

        # LAST_MODIFIED attribute
        [Parameter()]
        [Nullable[System.DateTime]]
        $ModifiedDate,

        # Other attributes
        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $Attributes
    )

    # Validate range of datetime
    $AddDate = if (Validate-DateTime $AddDate) { $AddDate }else { $null }
    $ModifiedDate = if (Validate-DateTime $ModifiedDate) { $ModifiedDate }else { $null }

    if ($Ensure -eq [Ensure]::Absent) {
        # The bookmark file does not exist
        if ([string]::IsNullOrWhiteSpace($Path) -or (-not (Test-Path -LiteralPath $Path -PathType Leaf))) {
            Write-Verbose -Message 'The bookmark file does not exist. No action taken.'
            return
        }
        else {
            # Read and parse the bookmark file
            $BookmarkContent = Get-Content -LiteralPath $Path -Raw -Encoding utf8
            $Reader = [BookmarksManager.NetscapeBookmarksReader]::new()
            $Bookmark = $Reader.Read($BookmarkContent)

            # Create new bookmark folders without target folder
            $NewBookmarkParent = [BookmarksManager.BookmarkFolder]::new()
            $Bookmark.AllFolders | ? { $_.Title -ne $Title } | % {
                $NewBookmarkParent.Add($_)
            }

            # Save the bookmark file
            Write-Verbose -Message 'Saving the bookmark file...'
            $Writer = [BookmarksManager.NetscapeBookmarksWriter]::new($NewBookmarkParent)
            $ParentFolder = Split-Path -Path $Path -Parent
            if (-not (Test-Path -LiteralPath $ParentFolder -PathType Container)) {
                $null = New-Item -ItemType Directory -Path $ParentFolder -Force
            }
            $Writer.ToString() | Out-File -FilePath $Path -Encoding utf8 -Force -NoNewline
            Write-Verbose -Message 'The bookmark file has been saved.'
        }
    }
    else {
        # if the bookmark file does not exist, create it
        if ([string]::IsNullOrWhiteSpace($Path) -or (-not (Test-Path -LiteralPath $Path -PathType Leaf))) {
            Write-Verbose -Message 'The bookmark file does not exist. Creating it...'
            $NewBookmarkParent = [BookmarksManager.BookmarkFolder]::new()
        }
        else {
            # Read and parse the bookmark file
            $BookmarkContent = Get-Content -LiteralPath $Path -Raw -Encoding utf8
            $Reader = [BookmarksManager.NetscapeBookmarksReader]::new()
            $NewBookmarkParent = $Reader.Read($BookmarkContent)
        }

        # Get target folder
        $Target = $NewBookmarkParent.AllFolders | ? { $_.Title -eq $Title } | select -First 1
        if ($null -eq $Target) {
            # if that does not exist, create it
            Write-Verbose -Message 'The target folder does not exist. Creating it...'
            $Target = [BookmarksManager.BookmarkFolder]::new()
            $NewBookmarkParent.Add($Target)
        }

        # Set title
        Write-Verbose -Message "Set the title to $Title"
        $Target.Title = $Title

        # Set ADD_DATE
        if ($null -ne $AddDate) {
            Write-Verbose -Message ('Set the ADD_DATE to {0}' -f $AddDate.ToString("yyyy-MM-dd'T'HH:mm:sszzz"))
            $Target.Added = $AddDate.ToUniversalTime()
        }

        # Set LAST_MODIFIED
        if ($null -ne $ModifiedDate) {
            Write-Verbose -Message ('Set the LAST_MODIFIED to {0}' -f $ModifiedDate.ToString("yyyy-MM-dd'T'HH:mm:sszzz"))
            $Target.LastModified = $ModifiedDate.ToUniversalTime()
        }

        # Set other attributes
        if ($null -ne $Attributes) {
            # Convert CimInstance[] to Generic.Dictionary<string, string>
            $HashTableAttributes = ConvertTo-HashTable -CimInstance $Attributes
            $StringAttributes = [System.Collections.Generic.Dictionary[[string], [string]]]::new()
            foreach ($key in $HashTableAttributes.Keys) {
                $StringAttributes.Add($key.ToString().ToLower(), $HashTableAttributes[$key].ToString())
            }

            # if the target folder has no attributes, add them
            if ($null -eq $Target.Attributes) {
                Write-Verbose -Message 'Set attributes'
                $Target.Attributes = $StringAttributes
            }
            else {
                # Add desired attributes to the target folder
                foreach ($key in $StringAttributes.Keys) {
                    Write-Verbose -Message ('Set the value of the {0} to {1}' -f $Key, $StringAttributes[$key])
                    if ($Target.Attributes.ContainsKey($key)) {
                        $Target.Attributes[$key] = $StringAttributes[$key]
                    }
                    else {
                        $Target.Attributes.Add($key, $StringAttributes[$key])
                    }
                }
            }
        }

        # Save the bookmark file
        Write-Verbose -Message 'Saving the bookmark file...'
        $Writer = [BookmarksManager.NetscapeBookmarksWriter]::new($NewBookmarkParent)
        $ParentFolder = Split-Path -Path $Path -Parent
        if (-not (Test-Path -LiteralPath $ParentFolder -PathType Container)) {
            $null = New-Item -ItemType Directory -Path $ParentFolder -Force
        }
        $Writer.ToString() | Out-File -FilePath $Path -Encoding utf8 -Force -NoNewline
        Write-Verbose -Message 'The bookmark file has been saved.'
    }
}


function Test-TargetResource {
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter()]
        [System.String]
        $Ensure = [Ensure]::Present,

        # Path to the bookmark file
        [Parameter(Mandatory = $true)]
        [System.String]
        $Path,

        # Title of the bookmark folder
        [Parameter(Mandatory = $true)]
        [System.String]
        $Title,

        # ADD_DATE attribute
        [Parameter()]
        [Nullable[System.DateTime]]
        $AddDate,

        # LAST_MODIFIED attribute
        [Parameter()]
        [Nullable[System.DateTime]]
        $ModifiedDate,

        # Other attributes
        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $Attributes
    )

    # Validate range of datetime
    $AddDate = if (Validate-DateTime $AddDate) { $AddDate }else { $null }
    $ModifiedDate = if (Validate-DateTime $ModifiedDate) { $ModifiedDate }else { $null }

    # Get current state
    $CurrentState = Get-TargetResource -Path $Path -Title $Title

    if ($CurrentState.Ensure -ne $Ensure) {
        Write-Verbose -Message ('The target resource is not in the expected state. Expected:{0}, but got {1}.' -f $Ensure, $CurrentState.Ensure)
        return $false
    }

    # The title of the bookmark folder is different, return False
    if ($Title -ne $CurrentState.Title) {
        Write-Verbose -Message ('The title of bookmark folder is different. Expected:{0}, but got {1}.' -f $Title, $CurrentState.Title)
        return $false
    }

    if ($null -ne $AddDate) {
        # The ADD_DATE attribute is different, return False
        if (($CurrentState.AddDate -isnot [datetime]) -or ($AddDate.ToUniversalTime() -ne $CurrentState.AddDate.ToUniversalTime())) {
            Write-Verbose -Message ('The ADD_DATE attribute is different. Expected:{0}, but got {1}.' -f $AddDate.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:sszzz"), $CurrentState.AddDate.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:sszzz"))
            return $false
        }
    }

    if ($null -ne $ModifiedDate) {
        # The LAST_MODIFIED attribute is different, return False
        if (($CurrentState.ModifiedDate -isnot [datetime]) -or ($ModifiedDate.ToUniversalTime() -ne $CurrentState.ModifiedDate.ToUniversalTime())) {
            Write-Verbose -Message ('The LAST_MODIFIED attribute is different. Expected:{0}, but got {1}.' -f $ModifiedDate.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:sszzz"), $CurrentState.ModifiedDate.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:sszzz"))
            return $false
        }
    }

    if ($null -ne $Attributes) {
        # Convert CimInstance[] to Generic.Dictionary<string, string>
        $HashTableAttributes = ConvertTo-HashTable -CimInstance $Attributes
        $StringAttributes = [System.Collections.Generic.Dictionary[[string], [string]]]::new()
        foreach ($key in $HashTableAttributes.Keys) {
            $StringAttributes.Add($key.ToString().ToLower(), $HashTableAttributes[$key].ToString())
        }

        foreach ($key in $StringAttributes.Keys) {
            # Some attributes are different, return False
            if (-not [object]::Equals($StringAttributes[$key], $CurrentState.Attributes[$key])) {
                Write-Verbose -Message ('The value of the {0} attribute is different. Expected:{1}, but got {2}.' -f $key, $StringAttributes[$key], $CurrentState.Attributes[$key])
                return $false
            }
        }
    }

    Write-Verbose -Message 'The target resource is in the expected state.'
    return $true
}


Export-ModuleMember -Function *-TargetResource