Libraries/Bootloader/Includes/Bootloader.refind.psm1


<#
.SYNOPSIS
Search for a refind configuration
 
.DESCRIPTION
Search into local partition for a refind.conf file.
Default search for a "refind.conf" file. If config name is overriden, use -Filename to search for custom filename.
 
.EXAMPLE
Find-OSBootloaderRefindCfg -Path "/boot"
 
.EXAMPLE
Find-OSBootloaderRefindCfg -Path "/boot" -Filename "myrefind.conf"
 
.NOTES
General notes
#>

function Find-OSBootloaderRefindCfg {
    [CmdletBinding()]
    [OutputType([String])]
    Param (
        # Path to start search
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][string]$Path,
        # Custom filename to search for
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][string]$Filename = "refind.conf",
        # Search as far as MaxDepth deep
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][uint32]$MaxDepth = 3
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        $filename = Get-ChildItem -Path $Path $Filename -Recurse -Depth $MaxDepth -ErrorAction SilentlyContinue
        return ${refindcfg}?.fullname
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
Get refind boot order
 
.DESCRIPTION
according to the documentation @url https://www.rodsbooks.com/refind/configfile.html
 
the key is 'default_selection' from config file.
but it can appear several times.
 
.PARAMETER Filename
Parameter description
 
.PARAMETER Content
Parameter description
 
.EXAMPLE
An example
 
.NOTES
General notes
#>

function Get-OSBootloaderRefindBootOrder {
    [CmdletBinding()]
    [OutputType([String])]
    Param (
        [Alias('ConfigFile')]
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false, ParameterSetName = 'FILENAME')][string]$Filename = "/boot/Refind/Refind.conf",
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false, ParameterSetName = 'CONTENT')][array]$Content
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        switch ($PSCmdlet.ParameterSetName) {
            'FILENAME' {
                $Content = Get-OSBootloaderRefindArray -Filename $Filename
            }
        }
        # the key is 'default_selection' from config file.
        # but it can appear several times.
        # according to the help
    }

    End {
        Write-LeaveFunction
    }
}

function Set-OSBootloaderRefindDefaultBoot {
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    Param (
        [Alias('ConfigFile')]
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][string]$Filename = "/boot/Refind/Refind.conf",
        [Parameter(Mandatory = $true, ValueFromPipeLine = $false)][string]$label
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        $Refind = (Get-Content $Filename -Raw) -replace "^DEFAULT .*", "DEFAULT $label"
        $Refind | Out-File $Filename
        return $?
    }

    End {
        Write-LeaveFunction
    }
}

function Get-OSBootloaderRefindHashtable {
    [CmdletBinding()]
    [OutputType([hashtable])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][string]$Filename
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        $refind = @{}
        # $content = (Get-Content $Filename | Select-String -Pattern "^[a-zA-Z_0-9]+" -AllMatches | Select-String "menuentry" -NotMatch) -replace '(^[a-zA-Z_0-9]+)', '${1} = ' | ConvertFrom-StringData
        # foreach ($key in $content.keys) {
        # $refind.Add($key, $content.$key)
        # }
        # $content = (Get-Content $Filename | Select-String -Pattern "^[a-zA-Z_0-9]+" -AllMatches | Select-String "menuentry" -NotMatch) -replace '(^[a-zA-Z_0-9]+)', '${1} = ' | ConvertFrom-StringData
        foreach ($line in (Get-Content $Filename)) {
            if ($line -match "^(?<name>[a-zA-Z_0-9]+)\s(?<value>.*)") {
                switch ($matches.name) {
                    'menuentry' { continue }
                    Default {
                        $refind.Add($matches.name, $matches.value)
                    }
                }
            }
        }
        $entries = (Get-Content -Raw $Filename) -split '\r?\n\r?\n' -match '^menuentry'
        $menuentries = @()
        foreach ($entry in $entries) {
            $menuentry = @{}
            # flatten menuentry
            $flatEntry = (($entry -split '\n' -replace '^\s+') -replace '\\', '/' | Select-String "^#" -NotMatch -AllMatches) -replace ' {' -replace '}'
            # convert to hashtable
            $hash = $flatEntry -replace '(^[a-zA-Z_0-9]+)', '${1} = ' | ConvertFrom-StringData
            foreach ($key in $hash.keys) {
                $menuentry.Add($key, $hash.$key)
            }
            $menuentries += $menuentry
        }
        $refind.Add("menuentries", $menuentries)
        return $refind
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
Convert a refind.conf file into a usable object
 
.DESCRIPTION
Load a refind.conf file into an array. We can not convert to a hastable as keys can be declared multiple times.
'default_selection' for example or 'initrd'.
 
.EXAMPLE
Get-OSBootloaderRefindArray -ConfigurationFile /boot/EFI/refind/refind.conf
 
.NOTES
General notes
#>

function Get-OSBootloaderRefindArray {
    [CmdletBinding()]
    [OutputType([array])]
    Param (
        # Full path to refind.conf to load
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][string]$Filename
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        $refind = @()
        # $content = (Get-Content $Filename | Select-String -Pattern "^[a-zA-Z_0-9]+" -AllMatches | Select-String "menuentry" -NotMatch) -replace '(^[a-zA-Z_0-9]+)', '${1} = ' | ConvertFrom-StringData
        # foreach ($key in $content.keys) {
        # $refind += @{$key = $content.$key}
        # }
        foreach ($line in (Get-Content $Filename)) {
            # # keep comments
            # if ($line -match "^#") {
            # $refind += $line
            # }

            if ($line -match "^(?<name>[a-zA-Z_0-9]+)\s(?<value>.*)") {
                switch ($matches.name) {
                    'menuentry' { continue }
                    Default {
                        $refind += @{$matches.name = $matches.value}
                    }
                }
            }
        }
        $entries = (Get-Content -Raw $Filename) -split '\r?\n\r?\n' -match '^menuentry'
        $menuentries = @()
        foreach ($entry in $entries) {
            $menuentry = @()
            foreach ($line in ($entry -split '\n' -replace '^\s+')) {
                if ($line -match "^(?<name>[a-zA-Z_0-9]+)\s(?<value>.*)") {
                    switch ($matches.name) {
                        'menuentry' {
                            $menuentry += @{$matches.name = $matches.value -replace ' {'}
                        }
                        Default {
                            $menuentry += @{$matches.name = $matches.value}
                        }
                    }
                }
            }
            $menuentries += @(, $menuentry)
        }
        $refind += @{"menuentries" = $menuentries}
        return $refind
    }

    End {
        Write-LeaveFunction
    }
}

function Get-OSBootloaderRefind {
    [CmdletBinding()]
    [OutputType([array])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][string]$Filename
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        return Get-OSBootloaderRefindArray @PSBoundParameters
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
Convert a refind configuration array to a form writable to a file
 
.DESCRIPTION
Convert a previously loader configuration in a form suitable to be written again
 
.EXAMPLE
$content | Out-OSBootloaderRefindConf | Out-File /boot/EFI/refind/refind.conf
 
.NOTES
General notes
#>

function Out-OSBootloaderRefindConf {
    [CmdletBinding()]
    [OutputType([String])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][array]$Content
    )
    Begin {
        Write-EnterFunction
        @"
#
# refind.conf
# Please read refind.conf-sample in the same directory to get explanations
# or consult official help @url https://www.rodsbooks.com/refind/configfile.html
#
"@

    }

    Process {

        for ($i = 0; $i -lt $Content.Count; $i++) {
            foreach ($key in $Content[$i].keys) {
                switch ($key) {
                    'menuentries' {
                        foreach ($menuentry in $Content.menuentries) {
                            ""
                            for ($j = 0; $j -lt $menuentry.Count; $j++) {
                                foreach ($mkey in $menuentry[$j].keys) {
                                    switch ($mkey) {
                                        'menuentry' {
                                            ""
                                            "menuentry $($menuentry[$j].$mkey) {"
                                        }
                                        Default {
                                            " $mkey $($menuentry[$j].$mkey)"
                                        }
                                    }
                                }
                            }
                            "}"
                        }
                    }
                    Default {
                        "$key $($Content[$i].$key)"
                    }
                }
            }
        }
    }

    End {
        ""
        Write-LeaveFunction
    }
}

function Get-OSBootloaderRefindBootEntries {
    [CmdletBinding()]
    [OutputType([String])]
    Param (
        # Custom filename to search on
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][string]$Filename,

        # Show all entries label
        [Parameter(Mandatory = $false, ParameterSetName = 'ALL')][switch]$All
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        $menuEntries = @()
        # $submenuEntries = @()
        if (Test-FileExist $Filename) {
            # parse grub.cfg
            # To extract correct §, we need to read the file with -Raw parameter
            # (?ms) sets regex options m (treats ^ and $ as line anchors) and s (makes . match \n (newlines) too`.
            # ^## .*? matches any line starting with ## and any subsequent characters *non-greedily* (non-greedy is '.*?' set of characters at the end of pattern).
            # -AllMatches to get... well... all matches
            $content = Get-Content -Raw $Filename | Select-String -Pattern '(?ms)^menuentry .*?^}$' -AllMatches
            # edevel("MESSAGES = " + $MESSAGES.Matches[0])
            # Write-Debug "MESSAGE = $MESSAGE"
            foreach ($msg in $content.Matches) {
                $menuEntry = [PSCustomObject]@{}
                $menuEntry | Add-Member -MemberType NoteProperty -name "Bootloader" -Value "refind"
                $menuEntry | Add-Member -MemberType NoteProperty -name "Filename" -Value $Filename
                if ($msg.value -match "^menuentry `"(?<label>.*?)`" .*") {
                    $menuEntry | Add-Member -MemberType NoteProperty -name "Label" -Value $Matches.label
                }
                if ($msg.value -match "`n\s*loader\s*(?<kernel>.*)") {
                    $menuEntry | Add-Member -MemberType NoteProperty -name "kernel" -Value $Matches.kernel
                }
                if ($msg.value -match "`n\s*initrd\s*(?<initrd>.*)") {
                    $menuEntry | Add-Member -MemberType NoteProperty -name "initrd" -Value $Matches.initrd
                }
                if ($msg.value -match "`n\s*options\s*(?<options>.*)") {
                    $menuEntry | Add-Member -MemberType NoteProperty -name "options" -Value $Matches.options
                }
                $menuEntries += $menuEntry
            }
        } else {
            return $null
        }

        return $menuEntries
    }

    End {
        Write-LeaveFunction
    }
}