KittlPartner-Intune.psm1

# Type imports -- indent needs to be 0!
Add-Type -ErrorAction Stop -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
 
public static class Wallpaper {
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
}
'@


# Import all class files
. "$PSScriptRoot\Classes\LibraryContext.ps1" # First import LibraryContext.ps1 explicitly
Get-ChildItem -Path "$PSScriptRoot\Classes" -Filter *.ps1 |
    Where-Object { $_.Name -ne 'LibraryContext.ps1' } |
    ForEach-Object { . $_.FullName } # Then import all other .ps1 files in the Classes folder except LibraryContext.ps1


# Shared context variable
$script:Context = $null

function Get-RunContext {
    $sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
    switch ($sid) {
        'S-1-5-18' { return 'System' }        # LocalSystem
        'S-1-5-19' { return 'LocalService' }  # (rare for Intune)
        'S-1-5-20' { return 'NetworkService' }# (rare for Intune)
        default    { return 'User' }
    }
}

function Initialize-KittlPartnerIntune {
    param(
        [string]$instanceName = "TestInstance",
        [string]$instanceVersion = "1.0.0",
        [string]$tempDir = $null,
        [string]$logPath = $null
    )

    # get running as
    $runContext = Get-RunContext

    #clean instanceName
    $instanceName = $instanceName -replace '[^a-zA-Z0-9]', '_'

    # store meta
    $meta = @{
        InstanceName = $instanceName
        InstanceVersion = $instanceVersion
        RunContext = $runContext
    }

    if (-not $tempDir) {
        if ($runContext -eq "User") {
            $tempDir = "${env:TEMP}\intune" 
        } else {
            $tempDir = "${env:SystemRoot}\Temp\intune"
        }
    }
    if (-not $logPath) { $logPath = "${TempDir}\${InstanceName}.log" }

    
    # Create empty context
    $context = [LibraryContext]::new() #$logger

    # Add meta
    $context.Meta = $meta

    # Create Managers
    $context.LogManager = [LogManager]::new($logPath)
    $context.AppxPackageManager =  [AppxPackageManager]::new($context)
    $context.BitlockerManager =    [BitlockerManager]::new($context)
    $context.DownloadManager =     [DownloadManager]::new($context)
    $context.ExeExecuteManager =   [ExeExecuteManager]::new($context)
    $context.FolderManager =       [FolderManager]::new($context)
    $context.GoogleChromeManager = [GoogleChromeManager]::new($context)
    $context.HpSoftwareManager =   [HpSoftwareManager]::new($context)
    $context.ImageManager =        [ImageManager]::new($context)
    $context.MsiManager =          [MsiManager]::new($context)
    $context.PowerManager =        [PowerManager]::new($context)
    $context.RegeditManager =      [RegeditManager]::new($context)
    $context.UacManager =          [UacManager]::new($context)
    $context.StartmenuManager =    [StartmenuManager]::new($context)
    $context.Win32ProductManager = [Win32ProductManager]::new($context)
    $context.WingetManager =       [WingetManager]::new($context)

    # Assign to module-level variable for reuse
    $script:Context = $context

    $context.LogManager.Log("Library for $($context.Meta['InstanceName']) (v$($context.Meta['InstanceVersion'])) initialized for $($context.Meta['RunContext']) with log file: $LogPath")
    return $context
}

function Get-Args {
    param(
        [object]$givenArgs, #string[] can't be used in case of empty $givenArgs
        [object]$validArgs, #hashtable can't be used in case of empty $validArgs
        [bool] $print = $true,
        [object] $context = $null #needs to be passed, idk why but using $context = Get-KittlPartnerContext is not working
    )

    $out = [ordered]@{}
    $positional = @()
    
    # check for non-empty inputs
    if (-not $givenArgs) {
        $givenArgs = @()
    }

    if ($validArgs) {

        # Initialize defaults (and false for switches)
        foreach ($k in $validArgs.Keys) {
            $def = $null
            if ($validArgs[$k].ContainsKey('Default')) { $def = $validArgs[$k].Default }
            elseif ($validArgs[$k].Type -eq [switch]) { $def = $false }
            $out[$k] = $def
        }

        for ($i = 0; $i -lt $givenArgs.Count; $i++) {
            $tok = $givenArgs[$i]

            if ($tok -notlike '-*') {
                $positional += $tok
                continue
            }

            # Trim leading dashes and split inline value if present
            $name = $tok.TrimStart('-')
            $value = $null
            if ($name.Contains('=')) {
                $parts = $name.Split('=', 2)
                $name, $value = $parts[0], $parts[1]
            } elseif ($name.Contains(':')) {
                $parts = $name.Split(':', 2)
                $name, $value = $parts[0], $parts[1]
            }

            if (-not $validArgs.ContainsKey($name)) {
                throw "Unknown argument: -$name"
            }

            $type = $validArgs[$name].Type
            if ($type -eq [switch]) {
                $out[$name] = $true
                continue
            }

            if ($null -eq $value) {
                if ($i + 1 -lt $givenArgs.Count -and $givenArgs[$i + 1] -notlike '-*') {
                    $i++
                    $value = $givenArgs[$i]
                } else {
                    throw "Missing value for -$name"
                }
            }

            # Basic type conversion
            if ($type -ne [string]) {
                try { $value = [Convert]::ChangeType($value, $type) } catch { }
            }
            $out[$name] = $value
        }

        # Required checks
        foreach ($k in $validArgs.Keys) {
            if ($validArgs[$k].ContainsKey('Required') -and $validArgs[$k].Required -and $null -eq $out[$k]) {
                throw "Missing required argument: -$k"
            }
        }

        if ($print) { # -and $context
            $listStr = ($out.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join "; "
            $context.LogManager.Log("Parsed script options (incl. defaults): $listStr")
        }
    }

    if ($positional.Count) { $out['__positional'] = $positional }

    return [pscustomobject]$out
}


function Get-KittlPartnerContext {
    return $script:Context
}


Export-ModuleMember -Function Initialize-KittlPartnerIntune, Get-KittlPartnerContext, Get-Args