GCCare.psm1

################################################################################
###### #####
###### GCCare Module #####
###### #####
################################################################################
<#
.SYNOPSIS
GCCare is a PowerShell module to maintain the Garmin Connect settings.
 
.DESCRIPTION
 
In addition, GCCare includes fitness-related utilities (for example:
Tanita CSV -> FIT conversion and Garmin Connect upload workflows).
 
.MODULE STRUCTURE
- Public\ : exported user commands
- Private\ : internal helper functions
- Common\ : shared utilities
 
.RUNTIME BEHAVIOR
On import, the module:
1. Initializes global script settings (dates, colors, paths, logging flags)
2. Loads all scripts from Private/Public/Common
3. Exports public functions (and aliases, if defined)
4. Optionally exports private functions when GCCare_EXPORT_PRIVATE=1
5. Ensures required folders exist under $env:PUBLIC\GCCare
6. Shows module banner/version and quick-start hints
7. Runs requirement checks (Python + pip packages)
 
.DEFAULT DIRECTORIES
- Logs: $env:PUBLIC\GCCare\Logs
- Temp: $env:PUBLIC\GCCare\Temp
- Config: $env:PUBLIC\GCCare\Config
- CleanUp: $env:PUBLIC\GCCare\CleanUp
- FitFiles: $env:PUBLIC\GCCare\FitFiles
 
.REQUIREMENTS
- PowerShell 7+ recommended
- Python available in PATH
- pip packages:
  - fit-tool
  - garminconnect
 
.NOTES
Author: HerrHozi | zimmermann.holger@live.de
#>


$Script:DateFormatLog = "yyyy-MM-dd HH:mm:ss.fff"
$Script:DateFormatSA = "yyyy-MM-dd HH:mm:ss"

$Script:WinVersion = [System.Environment]::OSVersion.Version.ToString()

$GGCModulePath = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$GGCModuleTopPath = Split-Path -Path $GGCModulePath -Parent
$GGCModuleName = $MyInvocation.MyCommand.ScriptBlock.Module.Name

$GGCModuleManifest = (Test-ModuleManifest -Path $(join-path $GGCModulePath -ChildPath "\$GGCModuleName.psd1"))
$GGCModuleLastUpdate = $GGCModuleManifest.PrivateData.PSData.LastUpdate

$script:GCCarePublicDir = Join-Path -Path $env:PUBLIC -ChildPath "GCCare"
$Script:GCCareLogDir = Join-Path -Path $script:GCCarePublicDir -ChildPath "Logs"
$Script:GCCareTempDir = Join-Path -Path $script:GCCarePublicDir -ChildPath "Temp"
$Script:GCCareConfigDir = Join-Path -Path $script:GCCarePublicDir -ChildPath "Config"
$Script:DefaultCleanUpFolder = Join-Path -Path $script:GCCarePublicDir -ChildPath "CleanUp"
$Script:DefaultFitFilesFolder = Join-Path -Path $script:GCCarePublicDir -ChildPath "FitFiles"


$Script:ASModuleLog = Join-Path -Path $Script:GCCareLogDir -ChildPath "GCCare.log"
$Script:ConfigFile = Join-Path -Path $Script:GCCareConfigDir -ChildPath "GCCare.json"


If ($PSVersionTable.PSVersion.Major -ge 7) {
    $Script:PoSH7 = $true
    # --- Global header color for all Format-Table outputs ---
    $PSStyle.Formatting.TableHeader = "`e[90m"   # DarkGray
    #Cyan → "e[96m"
    #White → "e[97m"
}

$Script:FGCIInfo = [System.ConsoleColor]::Magenta    # Additional info / side details
$Script:FGCMInfo = [System.ConsoleColor]::Yellow       # Main info (instead of Yellow → modern, easy to read)
$Script:FGCSInfo = [System.ConsoleColor]::DarkGray   # Secondary info / less important
$Script:FGCCommand = [System.ConsoleColor]::White      # Commands / executions
$Script:FGCQuestion = [System.ConsoleColor]::Cyan
$Script:FGCInput = [System.ConsoleColor]::Cyan
$Script:FGCDecisionPrompt = [System.ConsoleColor]::Cyan    # Questions / user input
$Script:FGCHighLight = [System.ConsoleColor]::Magenta      # Clear highlight text
$Script:FGCWarning = [System.ConsoleColor]::Cyan      # Warnings (classic Yellow)
$Script:FGCError = [System.ConsoleColor]::Red        # Errors (bright red tone, more visible)

$Script:ConsoleBGColor = [System.ConsoleColor]::Black
$Script:ConsoleFGColor = [System.ConsoleColor]::Gray

$Script:fgcS = "DarkGray"    # Switch - DarkGray
$Script:fgcC = "Yellow"      # Command
$Script:fgcV = "White"       # Value"DarkCyan" # Value Blue or Cyan
$Script:fgcF = "White"       # Foreground Color
$Script:fgcR = "Green"       # Result
$Script:fgcHeader = [System.ConsoleColor]::DarkGray

$Script:Yes = "Y"
$Script:No = "N"
$Script:EnableLogging = $false


##########################################################################

Set-Alias Pause Invoke-NextStep -Force

$ScriptDir = New-Object System.Collections.ArrayList

if (Test-Path("$GGCModulePath\Private")) {
    $PrivatePath = '{0}\Private\*.ps1' -f $GGCModulePath
    [VOID]$ScriptDir.Add($PrivatePath)
}
if (Test-Path("$GGCModulePath\Public")) {
    $PublicPath = '{0}\Public\*.ps1' -f $GGCModulePath
    [VOID]$ScriptDir.Add($PublicPath)
}
if (Test-Path("$GGCModulePath\Common")) {
    $CommonPath = '{0}\Common\*.ps1' -f $GGCModulePath
    [VOID]$ScriptDir.Add($CommonPath)
}

$Scripts = Get-ChildItem -Path $ScriptDir | Select-Object -ExpandProperty FullName

# Load all scripts in the module
foreach ($Script in $Scripts) {
    . $Script
}

#$SearchRecursive = $true
$SearchRootOnly = $false
$PublicScriptBlock = [ScriptBlock]::Create((Get-ChildItem -Path $PublicPath | Get-Content | Out-String))
$PublicFunctionAsts = $PublicScriptBlock.Ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $SearchRootOnly)
$PublicFunctions = $PublicFunctionAsts.Name

If ($env:GCCare_EXPORT_PRIVATE -eq 1) {
    $ExportPrivateFunctions = $true
}
else {
    $ExportPrivateFunctions = $false
}

# Export all functions including alias
if ($PublicFunctions) {
    foreach ($Function in $PublicFunctions) {
        $Alias = @()

        #Check if function has an alias
        $AliasFunc = $PublicFunctionAsts | Where-Object { $_.Name -eq $Function -and $_.Body.ParamBlock -and $_.Body.ParamBlock.Attributes } | Select-Object -First 1
        #Check if an alias was found, if it did save the alias name
        if ($AliasFunc) {
            $Alias = $AliasFunc.Body.ParamBlock.Attributes | Where-Object { $_.TypeName.Name -eq 'Alias' } | ForEach-Object { $_.PositionalArguments.Value } | Where-Object { $_ }
        }

        #If alias exist export function wiht alias
        if ($Alias) {
            Export-ModuleMember -Function $Function -Alias $Alias
        }
        else {
            Export-ModuleMember -Function $Function
        }

    }
}

if ($ExportPrivateFunctions -and $PrivatePath) {
    $PrivateFilePattern = Join-Path -Path $GGCModulePath -ChildPath 'Private\*.ps1'
    $PrivateFunctions = Get-Command -CommandType Function |
    Where-Object {
        $_.ScriptBlock -and
        $_.ScriptBlock.File -and
        ($_.ScriptBlock.File -like $PrivateFilePattern)
    } |
    Select-Object -ExpandProperty Name -Unique

    if ($PrivateFunctions) {
        foreach ($Function in $PrivateFunctions) {
            Export-ModuleMember -Function $Function -ErrorAction SilentlyContinue
        }
    }
}

Invoke-GCCareDirectory -Directory $Script:GCCareLogDir
Invoke-GCCareDirectory -Directory $Script:GCCareTempDir
Invoke-GCCareDirectory -Directory $Script:GCCareConfigDir
Invoke-GCCareDirectory -Directory (Join-Path -Path $script:GCCarePublicDir -ChildPath "MaliciousTools")
Invoke-GCCareDirectory -Directory $Script:DefaultCleanUpFolder
Invoke-GCCareDirectory -Directory $Script:DefaultFitFilesFolder

If (-not (Test-Path -Path $Script:ConfigFile)) {
    copy-item "$GGCModulePath\GCCare.json" -Destination $Script:ConfigFile
}


Show-PiskelFile -PiskelPath (Join-Path -Path $GGCModulePath -ChildPath "GCCare.piskel") -addshadow

#Write-Host "`n"
Write-Host " Description: $($GGCModuleManifest.Description) " -ForegroundColor Gray
Write-Host " Author: HerrHozi | Version: $($GGCModuleManifest.Version) | Last Update: $GGCModuleLastUpdate | $($GGCModuleManifest.Copyright)" -ForegroundColor Gray
Write-Host "`n get-command " -NoNewline -ForegroundColor DarkYellow
Write-Host "-Module " -ForegroundColor DarkGray -NoNewline
Write-Host "'GCCare'`n`n" -ForegroundColor DarkCyan


#Write-Host " Description: $($GGCModuleManifest.Description) " -ForegroundColor Gray
#Write-Host " Version: $($GGCModuleManifest.Version) | Last Update: $GGCModuleLastUpdate | Author: $($GGCModuleManifest.Author) " -ForegroundColor Gray
Write-Host "`n [>] Quick-Start: " -NoNewline -ForegroundColor Gray 

Write-HighlightedCode  -code "Update-TCXFile"
Write-HighlightedCode  -code " Convert-TanitaExportToFitFile"
Write-HighlightedCode  -code " New-GarminConnectSession"
Write-HighlightedCode  -code " Send-FitFileToGarminConnect"
Write-HighlightedCode  -code " Get-WeeklyBodyMetrics"
Write-HighlightedCode  -code " Invoke-TCXFileAnalysis | Select-Object Lap, TotalDistance, TotalTime, Laptime, MovingTime, NonMovingTime, AscentMeters, DescentMeters, TotalAscentMeters, TotalDescentMeters | ft"

Write-host "`n"
$host.ui.RawUI.WindowTitle = "$GGCModuleName - $($GGCModuleManifest.Version)"

Invoke-Output -Type H1 -Message "Checking GCCare Requirements ..."
Test-GCCarePythonInstalled
Test-GCCarePipPackageInstalled -PackageName "fit-tool"
Test-GCCarePipPackageInstalled -PackageName "garminconnect"
Write-host "`n"