Public/Import-LinuxInventory.ps1

function Import-LinuxInventory {
    <#
    .SYNOPSIS
        Bulk imports Linux servers into Active Directory from a CSV file.
 
    .DESCRIPTION
        Reads a CSV file containing Linux server details and registers each one as a
        computer object in AD by calling Register-LinuxServer for each row.
 
        Expected CSV columns: Name, IPAddress, OperatingSystem, Description, ManagedBy.
        OperatingSystem, Description, and ManagedBy are optional columns -- blank values
        are handled gracefully.
 
        Returns a summary object with imported, skipped, and error counts.
 
    .PARAMETER CsvPath
        Path to the CSV file. The file must exist and have .csv extension.
 
    .PARAMETER OrganizationalUnit
        Target OU for all imported servers. Defaults to "OU=Linux Servers" under the
        domain root.
 
    .PARAMETER Force
        Overwrite existing computer objects if they already exist.
 
    .EXAMPLE
        Import-LinuxInventory -CsvPath .\linux-servers.csv
 
        Imports all servers from the CSV into the default OU.
 
    .EXAMPLE
        Import-LinuxInventory -CsvPath C:\data\servers.csv -Force
 
        Imports servers, overwriting any existing entries.
 
    .EXAMPLE
        Import-LinuxInventory -CsvPath .\servers.csv -OrganizationalUnit "OU=Linux Servers,OU=Servers,DC=contoso,DC=com"
 
        Imports into a specific OU.
 
    .NOTES
        Requires: ActiveDirectory module (RSAT).
        CSV template:
            Name,IPAddress,OperatingSystem,Description,ManagedBy
            web-prod-01,10.1.2.50,Ubuntu 22.04 LTS,Production web server,jsmith
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0)]
        [ValidateScript({
            if (-not (Test-Path $_)) { throw "CSV file not found: $_" }
            if ($_ -notmatch '\.csv$') { throw "File must have .csv extension: $_" }
            $true
        })]
        [string]$CsvPath,

        [string]$OrganizationalUnit,

        [switch]$Force
    )

    begin {
        Import-Module ActiveDirectory -ErrorAction Stop

        $importedCount = 0
        $skippedCount  = 0
        $errorCount    = 0
        $errors        = [System.Collections.Generic.List[string]]::new()
    }

    process {
        $csvData = Import-Csv -Path $CsvPath -ErrorAction Stop
        $totalRows = @($csvData).Count
        Write-Verbose "Loaded $totalRows rows from $CsvPath"

        if ($totalRows -eq 0) {
            Write-Warning "CSV file is empty: $CsvPath"
            return
        }

        # Validate required columns
        $headers = ($csvData | Get-Member -MemberType NoteProperty).Name
        foreach ($required in @('Name', 'IPAddress')) {
            if ($required -notin $headers) {
                throw "CSV is missing required column '$required'. Found columns: $($headers -join ', ')"
            }
        }

        $rowIndex = 0
        foreach ($row in $csvData) {
            $rowIndex++
            Write-Verbose "Processing row $rowIndex of $totalRows : $($row.Name)"

            # Skip rows with missing mandatory fields
            if ([string]::IsNullOrWhiteSpace($row.Name) -or [string]::IsNullOrWhiteSpace($row.IPAddress)) {
                Write-Warning "Row $rowIndex skipped -- Name or IPAddress is blank."
                $skippedCount++
                continue
            }

            # Build the parameter splat
            $regParams = @{
                Name      = $row.Name.Trim()
                IPAddress = $row.IPAddress.Trim()
            }

            if ($OrganizationalUnit) { $regParams['OrganizationalUnit'] = $OrganizationalUnit }
            if ($Force)              { $regParams['Force'] = $true }

            # Optional columns
            if (-not [string]::IsNullOrWhiteSpace($row.OperatingSystem)) {
                $regParams['OperatingSystem'] = $row.OperatingSystem.Trim()
            }
            if ($headers -contains 'OperatingSystemVersion' -and -not [string]::IsNullOrWhiteSpace($row.OperatingSystemVersion)) {
                $regParams['OperatingSystemVersion'] = $row.OperatingSystemVersion.Trim()
            }
            if (-not [string]::IsNullOrWhiteSpace($row.Description)) {
                $regParams['Description'] = $row.Description.Trim()
            }
            if (-not [string]::IsNullOrWhiteSpace($row.ManagedBy)) {
                $regParams['ManagedBy'] = $row.ManagedBy.Trim()
            }

            try {
                $result = Register-LinuxServer @regParams
                if ($result) {
                    $importedCount++
                    Write-Verbose "Registered: $($row.Name)"
                }
                else {
                    $skippedCount++
                }
            }
            catch {
                $errorCount++
                $errorMsg = "Row $rowIndex ($($row.Name)): $_"
                $errors.Add($errorMsg)
                Write-Warning $errorMsg
            }
        }
    }

    end {
        $summary = [PSCustomObject]@{
            CsvPath   = $CsvPath
            Total     = $totalRows
            Imported  = $importedCount
            Skipped   = $skippedCount
            Errors    = $errorCount
            ErrorList = $errors
        }

        Write-Verbose "Import complete -- Imported: $importedCount, Skipped: $skippedCount, Errors: $errorCount"
        $summary
    }
}