# ##########################################################
#
# PollMe CSV Copy Script
# Copies new CSV/TXT files from a network share to a staging destination.
# Compatible with PSMan (scheduled or manual runs).
#
# PSMan Variable Injection
# The variables below are auto-discovered when you add this script to PSMan.
# PSMan will pre-populate them in the Variables tab (empty by default).
#
#PSMan-WatchedLog: CopyLog C:\Program Files (x86)\SINT\PSMan\Logs\EGMDatatoSINT_CopyLog.txt
#PSMan-WatchedLog: LastRunTime C:\Program Files (x86)\SINT\PSMan\Logs\lastRunTimestamp.txt
#
#PSMan-Variable: sourceUNC String \\Fill-Source-Path\Here
#PSMan-Variable: SUsername
#PSMan-Variable: SPassword Secret
#PSMan-Variable: destinationPath String \\Destination-Path\Here
#PSMan-Variable: DUsername
#PSMan-Variable: DPassword Secret
#PSMan-Variable: fileExtension String *.csv
#PSMan-Variable: logPath String C:\Program Files (x86)\SINT\PSMan\Logs
#
# Steps to Implement
#
# 1. In PSMan, create a new script pointing to this file.
# 2. Set each variable value in PSMan's Variables tab.
# 3. Do NOT set a Credential Profile on the script — credentials are
#    handled entirely via the variables above.
# 4. Optionally set a schedule in PSMan (default: every 5 minutes).
#
# ##########################################################

param(
    [string]$sourceUNC,
    [string]$SUsername,
    [string]$SPassword,
    [string]$destinationPath,
    [string]$DUsername,
    [string]$DPassword,
    [string]$fileExtension = "*.csv",
    [string]$logPath = "C:\Program Files (x86)\SINT\PSMan\Logs"
)

############################################################
# DO NOT CHANGE BELOW THIS LINE
############################################################

$altExtension     = if ($fileExtension -eq "*.csv") { "*.txt" } else { "*.csv" }
$logFile          = "$logPath\EGMDatatoSINT_CopyLog.txt"
$timestampFile    = "$logPath\lastRunTimestamp.txt"
$currentTimestamp = Get-Date

if (-not [System.IO.Directory]::Exists($logPath)) {
    [System.IO.Directory]::CreateDirectory($logPath) | Out-Null
}

[System.IO.File]::AppendAllText($logFile,
    "===== Script started at: $currentTimestamp =====" + [System.Environment]::NewLine)

function Log([string]$msg) {
    Write-Host $msg
    [System.IO.File]::AppendAllText($logFile, $msg + [System.Environment]::NewLine)
}

# ── WNetAddConnection2 helper ─────────────────────────────
# Runs on the calling thread — unlike a subprocess, this respects any
# thread-level impersonation and works correctly from service accounts.
# Multiple scripts can connect to the same server simultaneously.
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public static class WNetHelper {
    [DllImport("mpr.dll", CharSet = CharSet.Unicode)]
    private static extern int WNetAddConnection2(
        ref NETRESOURCE lpNetResource,
        string lpPassword, string lpUserName, uint dwFlags);

    [DllImport("mpr.dll", CharSet = CharSet.Unicode)]
    public static extern int WNetCancelConnection2(
        string lpName, uint dwFlags, bool fForce);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct NETRESOURCE {
        public uint dwScope, dwType, dwDisplayType, dwUsage;
        public string lpLocalName, lpRemoteName, lpComment, lpProvider;
    }

    public static int Connect(string remote, string user, string pass) {
        var nr = new NETRESOURCE { dwType = 1, lpRemoteName = remote };
        return WNetAddConnection2(ref nr, pass, user, 0);
    }

    public static int ConnectDrive(string letter, string remote, string user, string pass) {
        var nr = new NETRESOURCE {
            dwType = 1, lpLocalName = letter + ":", lpRemoteName = remote };
        return WNetAddConnection2(ref nr, pass, user, 0);
    }
}
"@ -Language CSharp -ErrorAction Stop

# ── Connect-Share ─────────────────────────────────────────
# Tries UNC (no drive letter), then drive letter, then unauthenticated.
# Returns PSCustomObject { ActivePath; MappedDrive }
function Connect-Share {
    param(
        [string]$UNC,
        [string]$Username,
        [string]$Password,
        [string]$DriveLetter,
        [string]$Label
    )

    $activePath  = $null
    $mappedDrive = $null

    # Method 1 — WNet UNC, no drive letter
    if ($Username -and $Password) {
        Log "[$Label] Method 1: WNetAddConnection2 UNC ($UNC as $Username)..."
        [WNetHelper]::WNetCancelConnection2($UNC, 0, $true) | Out-Null
        $rc = [WNetHelper]::Connect($UNC, $Username, $Password)
        Log "  result: $rc $(if ($rc -eq 0) { 'OK' } else { "Error $rc" })"
        if ($rc -eq 0) {
            try {
                $probe = [System.IO.Directory]::GetFiles($UNC, $fileExtension)
                Log "  Probe: $($probe.Length) $fileExtension files visible"
                $activePath = $UNC
            } catch {
                Log "  Probe failed: $_"
                [WNetHelper]::WNetCancelConnection2($UNC, 0, $true) | Out-Null
            }
        }
    }

    # Method 2 — WNet drive letter fallback
    if (($activePath -eq $null) -and $Username -and $Password -and $DriveLetter) {
        Log "[$Label] Method 2: WNetAddConnection2 drive ${DriveLetter}: ($UNC as $Username)..."
        [WNetHelper]::WNetCancelConnection2("${DriveLetter}:", 0, $true) | Out-Null
        [WNetHelper]::WNetCancelConnection2($UNC, 0, $true) | Out-Null
        $rc2 = [WNetHelper]::ConnectDrive($DriveLetter, $UNC, $Username, $Password)
        Log "  result: $rc2 $(if ($rc2 -eq 0) { 'OK' } else { "Error $rc2" })"
        if ($rc2 -eq 0) {
            try {
                $probe2 = [System.IO.Directory]::GetFiles("${DriveLetter}:\", $fileExtension)
                Log "  Probe: $($probe2.Length) $fileExtension files visible"
                $activePath  = "${DriveLetter}:\"
                $mappedDrive = $DriveLetter
            } catch {
                Log "  Probe failed: $_"
                [WNetHelper]::WNetCancelConnection2("${DriveLetter}:", 0, $true) | Out-Null
            }
        }
    }

    # Method 3 — unauthenticated UNC
    if ($activePath -eq $null) {
        Log "[$Label] Method 3: unauthenticated UNC access to $UNC..."
        if ([System.IO.Directory]::Exists($UNC)) {
            $activePath = $UNC
            Log "  UNC reachable without credentials."
        } else {
            Log "  UNC not reachable."
        }
    }

    [PSCustomObject]@{ ActivePath = $activePath; MappedDrive = $mappedDrive }
}

# ── Connect source ────────────────────────────────────────
$srcResult = Connect-Share -UNC $sourceUNC -Username $SUsername -Password $SPassword `
                           -DriveLetter "Z" -Label "source"

if ($srcResult.ActivePath -eq $null) {
    Log "FATAL: All source access methods failed. Aborting."
    throw "FATAL: Could not access source path '$sourceUNC'. Aborting."
}

$activePath        = $srcResult.ActivePath
$mappedDriveLetter = $srcResult.MappedDrive
Log "Active source path: $activePath"

# ── Connect destination ───────────────────────────────────
if ($DUsername -and $DPassword) {
    Log "Connecting to destination share as $DUsername..."
    [WNetHelper]::WNetCancelConnection2($destinationPath, 0, $true) | Out-Null
    $rc = [WNetHelper]::Connect($destinationPath, $DUsername, $DPassword)
    Log "  result: $rc $(if ($rc -eq 0) { 'OK' } else { "Error $rc" })"
    if ($rc -ne 0) {
        Log "FATAL: Could not connect to destination share. Aborting."
        throw "FATAL: Could not access destination path '$destinationPath'. Aborting."
    }
} else {
    Log "No destination credentials provided — attempting passthrough access."
}

# ── Ensure destination exists ─────────────────────────────
if (-not [System.IO.Directory]::Exists($destinationPath)) {
    [System.IO.Directory]::CreateDirectory($destinationPath) | Out-Null
}

# ── Sanity check ──────────────────────────────────────────
$destTarget = [System.IO.Directory]::GetFiles($destinationPath, $fileExtension)
$destAlt    = [System.IO.Directory]::GetFiles($destinationPath, $altExtension)
if ($destTarget.Length -eq 0 -and $destAlt.Length -gt 0) {
    Log "WARNING: Destination has $($destAlt.Length) $altExtension file(s) but no $fileExtension — verify fileExtension variable."
} elseif ($destTarget.Length -eq 0 -and $destAlt.Length -eq 0) {
    Log "INFO: Destination is currently empty."
} else {
    Log "INFO: Destination has $($destTarget.Length) $fileExtension and $($destAlt.Length) $altExtension file(s)."
}

# ── Read last-run timestamp ───────────────────────────────
$lastRunTimestamp = [datetime]"2000-01-01 00:00:00"
if ([System.IO.File]::Exists($timestampFile)) {
    $raw     = [System.IO.File]::ReadAllText($timestampFile).Trim()
    $formats = @('dddd, dd MMMM yyyy h:mm:ss tt', 'MM/dd/yyyy HH:mm:ss', 'yyyy-MM-ddTHH:mm:ss')
    $parsed  = $false
    foreach ($fmt in $formats) {
        try {
            $lastRunTimestamp = [datetime]::ParseExact($raw, $fmt, $null)
            $parsed = $true; break
        } catch { }
    }
    if (-not $parsed) { Log "Warning: could not parse timestamp '$raw', using default." }
}

$lastRunTimestampMinusOne = $lastRunTimestamp.AddDays(-1)
Log "Copy started at  : $currentTimestamp"
Log "Last run was at  : $lastRunTimestamp"

# ── Enumerate and copy files ──────────────────────────────
$allFilePaths = [System.IO.Directory]::GetFiles($activePath, $fileExtension)
Log "Total $fileExtension files present: $($allFilePaths.Length)"

if ($allFilePaths.Length -eq 0) {
    $altPaths = [System.IO.Directory]::GetFiles($activePath, $altExtension)
    if ($altPaths.Length -gt 0) {
        Log "WARNING: No $fileExtension files found, but $($altPaths.Length) $altExtension file(s) exist — check fileExtension variable."
    } else {
        Log "INFO: No $fileExtension or $altExtension files found in source."
    }
}

$filesToCopy = @()
foreach ($fp in $allFilePaths) {
    $fi = [System.IO.FileInfo]::new($fp)
    if ($fi.LastWriteTime -gt $lastRunTimestampMinusOne) { $filesToCopy += $fi }
}
Log "$fileExtension files to copy (modified after $lastRunTimestampMinusOne): $($filesToCopy.Length)"

$fileCount = 0
foreach ($file in $filesToCopy) {
    $dest = [System.IO.Path]::Combine($destinationPath, $file.Name)
    try {
        Log "Copying: $($file.FullName)"
        [System.IO.File]::Copy($file.FullName, $dest, $true)
        Log "Copied : $($file.Name)"
        $fileCount++
    } catch {
        Log "FAILED : $($file.Name) — $_"
    }
}

Log "Total files copied: $fileCount"

# ── Save timestamp ────────────────────────────────────────
[System.IO.File]::WriteAllText($timestampFile,
    $currentTimestamp.ToString('dddd, dd MMMM yyyy h:mm:ss tt'))

Log "Copy completed at: $(Get-Date)"

# ── Disconnect ────────────────────────────────────────────
if ($mappedDriveLetter) {
    $r = [WNetHelper]::WNetCancelConnection2("${mappedDriveLetter}:", 0, $true)
    Log "Removed drive ${mappedDriveLetter}:: result=$r"
} elseif ($activePath -eq $sourceUNC) {
    $r = [WNetHelper]::WNetCancelConnection2($sourceUNC, 0, $true)
    Log "Removed source UNC session: result=$r"
}

if ($DUsername -and $DPassword) {
    $r = [WNetHelper]::WNetCancelConnection2($destinationPath, 0, $true)
    Log "Removed destination UNC session: result=$r"
}

Log "-=-=-=-=-=-==-=-=--=-=-==-=-=-=-`n"
