Dictionaries/Dict.OS/Includes/Bootloader/Includes/Bootloader.Grub2.psm1

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

function Find-OSBootloaderGrub2Cfg {
    [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 = "grub.cfg",
        # Search as far as MaxDepth deep
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][uint32]$MaxDepth = 3
    )
    Begin {
        Write-PwShFwOSEnterFunction
    }

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

    End {
        Write-PwShFwOSLeaveFunction
    }
}
function Get-OSBootloaderGrub2DefaultBoot {
    [CmdletBinding()]
    [OutputType([String])]
    Param (
        [Alias('ConfigFile')]
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][string]$Grub2Cfg = "/boot/grub/Grub2.cfg"
    )
    Begin {
        Write-PwShFwOSEnterFunction
    }

    Process {
        $rc = (Get-Content $Grub2Cfg | select-string "^LABEL") | ForEach-Object { $_ -match "^LABEL\s*(?<label>\w+)" }
        if ($rc) {
            return $Matches.label
        } else {
            return $null
        }
    }

    End {
        Write-PwShFwOSLeaveFunction
    }
}

function Set-OSBootloaderGrub2DefaultBoot {
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    Param (
        [Alias('ConfigFile')]
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][string]$Grub2Cfg = "/boot/grub/Grub2.cfg",
        [Parameter(Mandatory = $true, ValueFromPipeLine = $false)][string]$label
    )
    Begin {
        Write-PwShFwOSEnterFunction
    }

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

    End {
        Write-PwShFwOSLeaveFunction
    }
}

function Get-OSBootloaderGrub2BootEntries {
    [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-PwShFwOSEnterFunction
    }

    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
            # Write-PwShFwOSDevel "MESSAGES = " + $MESSAGES.Matches[0]
            # Write-PwShFwOSDebug "MESSAGE = $MESSAGE"
            foreach ($msg in $content.Matches) {
                $menuEntry = [PSCustomObject]@{}
                $menuEntry | Add-Member -MemberType NoteProperty -name "Bootloader" -Value "grub2"
                $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*linux\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
                }
                $menuEntries += $menuEntry
            }
        } else {
            return $null
        }

        return $menuEntries
    }

    End {
        Write-PwShFwOSLeaveFunction
    }
}

<#
.SYNOPSIS
Abalyze grub.con file and return an object
 
.DESCRIPTION
Load a grub.con file into an object. All parameters are kept as is. Menuentries are stored in an array.
 
.EXAMPLE
$grub2 = Get-OSBootloaderGrub2Object -Filename /boot/grub/grub.con
 
.NOTES
General notes
#>

function Get-OSBootloaderGrub2Object {
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    Param (
        # Full path to grub.con to load
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][string]$Filename
    )
    Begin {
        Write-PwShFwOSEnterFunction
    }

    Process {
        $grub2 = @{}
        # $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) {
        # $grub2.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 "^(?<key>[a-zA-Z_0-9]+)\s(?<value>.*)") {
                $key = $matches.key
                $value = $matches.value
                switch ($key) {
                    'menuentry' { continue }
                    Default {
                        if ($grub2.$key) {
                            if ($grub2.$key -is [array]) {
                                $grub2.$key += $value
                            } else {
                                $oldValue = $grub2.$key
                                $grub2.$key = @($oldValue, $value)
                            }
                        } else {
                            $grub2.$key = $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
        # }

        $menuentries = @()
        $entries = Get-Content -Raw $Filename | Select-String -Pattern '(?ms)^menuentry .*?^}$' -AllMatches
        # Write-PwShFwOSDevel "MESSAGES = " + $MESSAGES.Matches[0]
        # Write-PwShFwOSDebug "MESSAGE = $MESSAGE"
        foreach ($entry in $entries.Matches) {
            $menuEntry = @{}
            # save raw entry definition to lose nothing
            $menuEntry.raw = $entry.value
            if ($entry.value -match "^menuentry `"(?<label>.*?)`" .*") {
                $menuEntry.Label = $Matches.label
            }
            $options = $entry.value | Select-String -Pattern '\n\s+(?<k>[\S.]*)\s+(?<v>.*)' -AllMatches
            foreach ($option in $options.Matches) {
                if ($option.Groups['k'].Value -like "#*") {
                    # this is a comment
                    continue
                }
                $key = $option.Groups['k'].Value
                $value = $option.Groups['v'].Value
                if ($menuentry.$key) {
                    if ($menuentry.$key -is [array]) {
                        $menuentry.$key += $value
                    } else {
                        $oldValue = $menuentry.$key
                        $menuentry.$key = @($oldValue, $value)
                    }
                } else {
                    $menuentry.$key = $value
                }
            }
            $menuEntries += [PSCustomObject]$menuEntry
        }
        $grub2.menuentries = $menuentries
        return [PSCustomObject]$grub2
    }

    End {
        Write-PwShFwOSLeaveFunction
    }
}