412 lines
13 KiB
Go
412 lines
13 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"monitoring-agent-win/config"
|
|
"monitoring-agent-win/service"
|
|
)
|
|
|
|
const (
|
|
// ExeName is the name of the executable
|
|
ExeName = "winagentUSM.exe"
|
|
)
|
|
|
|
// Command-line flags
|
|
var (
|
|
serviceFlag = flag.String("service", "", "Service command: install, start, stop, restart, remove, run")
|
|
configFlag = flag.String("config", "", "Path to configuration file (used with -service install)")
|
|
apiKeyFlag = flag.String("api-key", "", "API key for authentication")
|
|
urlFlag = flag.String("url", "", "Server URL")
|
|
debugFlag = flag.String("debug", "", "Enable or disable debug logging (true/false)")
|
|
timezoneFlag = flag.String("timezone", "", "Timezone (e.g., Europe/London, America/New_York)")
|
|
checkIntervalFlag = flag.String("check-interval", "", "Health check interval in minutes (0 to disable)")
|
|
sessionLogSizeFlag = flag.String("session-log-size", "", "Session log rotation size in MB (0 to disable, max 20)")
|
|
errorLogSizeFlag = flag.String("error-log-size", "", "Error log rotation size in MB (0 to disable, max 20)")
|
|
eventLogSizeFlag = flag.String("event-log-size", "", "Event CSV log rotation size in MB (0 to disable, max 20)")
|
|
helpFlag = flag.Bool("help", false, "Display help information")
|
|
)
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
// Display help if requested
|
|
if *helpFlag {
|
|
displayHelp()
|
|
return
|
|
}
|
|
|
|
// Get configuration
|
|
cfg := config.GetConfig()
|
|
|
|
// Determine config file path - first check if it exists in install dir, otherwise use current directory
|
|
defaultConfigFile := filepath.Join(cfg.InstallDir, "config.ini")
|
|
currentDirConfig := "./config.ini"
|
|
|
|
var configFile string
|
|
if _, err := os.Stat(defaultConfigFile); err == nil {
|
|
configFile = defaultConfigFile
|
|
} else if _, err := os.Stat(currentDirConfig); err == nil {
|
|
configFile = currentDirConfig
|
|
} else {
|
|
// Use default location if neither exists
|
|
configFile = defaultConfigFile
|
|
}
|
|
|
|
// Handle service commands
|
|
if *serviceFlag != "" {
|
|
handleServiceCommand(*serviceFlag, configFile, *configFlag)
|
|
return
|
|
}
|
|
|
|
// Check if any config update flags are provided
|
|
hasConfigFlags := *apiKeyFlag != "" || *urlFlag != "" || *debugFlag != "" || *timezoneFlag != "" || *checkIntervalFlag != "" ||
|
|
*sessionLogSizeFlag != "" || *errorLogSizeFlag != "" || *eventLogSizeFlag != ""
|
|
|
|
// If config update flags are provided, load existing config first
|
|
if hasConfigFlags {
|
|
// Load existing configuration from file if it exists
|
|
if err := cfg.Load(configFile); err != nil {
|
|
fmt.Printf("Failed to load existing configuration: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// Update config if flags provided
|
|
configUpdated := false
|
|
restartService := false
|
|
|
|
if *apiKeyFlag != "" {
|
|
cfg.UpdateAPIKey(*apiKeyFlag)
|
|
configUpdated = true
|
|
restartService = true
|
|
}
|
|
if *urlFlag != "" {
|
|
cfg.UpdateServerURL(*urlFlag)
|
|
configUpdated = true
|
|
restartService = true
|
|
}
|
|
if *debugFlag != "" {
|
|
debugValue, err := strconv.ParseBool(*debugFlag)
|
|
if err != nil {
|
|
fmt.Printf("Invalid debug value: %s. Must be 'true' or 'false'.\n", *debugFlag)
|
|
os.Exit(1)
|
|
}
|
|
cfg.UpdateDebugLogs(debugValue)
|
|
configUpdated = true
|
|
restartService = true
|
|
}
|
|
if *timezoneFlag != "" {
|
|
cfg.UpdateTimezone(*timezoneFlag)
|
|
configUpdated = true
|
|
restartService = true
|
|
}
|
|
if *checkIntervalFlag != "" {
|
|
intervalValue, err := strconv.Atoi(*checkIntervalFlag)
|
|
if err != nil {
|
|
fmt.Printf("Invalid check-interval value: %s. Must be a number.\n", *checkIntervalFlag)
|
|
os.Exit(1)
|
|
}
|
|
if intervalValue < 0 {
|
|
fmt.Printf("Invalid check-interval value: %d. Must be 0 or greater.\n", intervalValue)
|
|
os.Exit(1)
|
|
}
|
|
cfg.UpdateHealthCheckInterval(intervalValue)
|
|
configUpdated = true
|
|
restartService = true
|
|
}
|
|
if *sessionLogSizeFlag != "" {
|
|
sizeValue, err := strconv.Atoi(*sessionLogSizeFlag)
|
|
if err != nil {
|
|
fmt.Printf("Invalid session-log-size value: %s. Must be a number.\n", *sessionLogSizeFlag)
|
|
os.Exit(1)
|
|
}
|
|
if sizeValue < 0 || sizeValue > 20 {
|
|
fmt.Printf("Invalid session-log-size value: %d. Must be between 0 and 20 MB.\n", sizeValue)
|
|
os.Exit(1)
|
|
}
|
|
cfg.UpdateSessionLogRotationSize(sizeValue)
|
|
configUpdated = true
|
|
restartService = true
|
|
}
|
|
if *errorLogSizeFlag != "" {
|
|
sizeValue, err := strconv.Atoi(*errorLogSizeFlag)
|
|
if err != nil {
|
|
fmt.Printf("Invalid error-log-size value: %s. Must be a number.\n", *errorLogSizeFlag)
|
|
os.Exit(1)
|
|
}
|
|
if sizeValue < 0 || sizeValue > 20 {
|
|
fmt.Printf("Invalid error-log-size value: %d. Must be between 0 and 20 MB.\n", sizeValue)
|
|
os.Exit(1)
|
|
}
|
|
cfg.UpdateErrorLogRotationSize(sizeValue)
|
|
configUpdated = true
|
|
restartService = true
|
|
}
|
|
if *eventLogSizeFlag != "" {
|
|
sizeValue, err := strconv.Atoi(*eventLogSizeFlag)
|
|
if err != nil {
|
|
fmt.Printf("Invalid event-log-size value: %s. Must be a number.\n", *eventLogSizeFlag)
|
|
os.Exit(1)
|
|
}
|
|
if sizeValue < 0 || sizeValue > 20 {
|
|
fmt.Printf("Invalid event-log-size value: %d. Must be between 0 and 20 MB.\n", sizeValue)
|
|
os.Exit(1)
|
|
}
|
|
cfg.UpdateEventLogRotationSize(sizeValue)
|
|
configUpdated = true
|
|
restartService = true
|
|
}
|
|
|
|
// Save config if updated
|
|
if configUpdated {
|
|
if err := cfg.Save(configFile); err != nil {
|
|
fmt.Printf("Failed to save configuration: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
fmt.Println("Configuration updated successfully.")
|
|
|
|
// Restart service if it was a configuration change that affects the running service
|
|
if restartService {
|
|
fmt.Println("Restarting service to apply changes...")
|
|
if err := service.StopService(); err != nil {
|
|
fmt.Printf("Warning: Failed to stop service: %v\n", err)
|
|
} else {
|
|
fmt.Println("Service stopped.")
|
|
}
|
|
|
|
if err := service.StartService(); err != nil {
|
|
fmt.Printf("Failed to start service: %v\n", err)
|
|
os.Exit(1)
|
|
} else {
|
|
fmt.Println("Service restarted successfully.")
|
|
fmt.Println("Health check will be performed automatically after restart.")
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// If no flags provided, display help
|
|
if flag.NFlag() == 0 {
|
|
displayHelp()
|
|
return
|
|
}
|
|
}
|
|
|
|
// handleServiceCommand handles service commands
|
|
func handleServiceCommand(cmd, configFile, externalConfigFile string) {
|
|
var err error
|
|
|
|
// Get configuration
|
|
cfg := config.GetConfig()
|
|
|
|
// Make sure the config directory exists
|
|
if err := os.MkdirAll(filepath.Dir(configFile), 0755); err != nil {
|
|
fmt.Printf("Failed to create config directory: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// For install command, handle config file loading
|
|
if strings.ToLower(cmd) == "install" {
|
|
var sourceConfigFile string
|
|
|
|
// Determine which config file to use as source
|
|
if externalConfigFile != "" {
|
|
// Use external config file if provided
|
|
sourceConfigFile = externalConfigFile
|
|
fmt.Printf("Using external config file: %s\n", sourceConfigFile)
|
|
|
|
// Check if the external config file exists
|
|
if _, err := os.Stat(sourceConfigFile); os.IsNotExist(err) {
|
|
fmt.Printf("External config file does not exist: %s\n", sourceConfigFile)
|
|
os.Exit(1)
|
|
}
|
|
} else {
|
|
// Check if config.ini exists in current directory
|
|
currentDirConfig := "./config.ini"
|
|
if _, err := os.Stat(currentDirConfig); err == nil {
|
|
sourceConfigFile = currentDirConfig
|
|
fmt.Printf("Using config file from current directory: %s\n", sourceConfigFile)
|
|
} else {
|
|
// Use default config (will be created)
|
|
sourceConfigFile = ""
|
|
fmt.Println("Using default configuration")
|
|
}
|
|
}
|
|
|
|
// Load configuration from source
|
|
if sourceConfigFile != "" {
|
|
if err := cfg.LoadFromSource(sourceConfigFile); err != nil {
|
|
fmt.Printf("Failed to load source configuration: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
} else {
|
|
// Load default config to ensure defaults are set
|
|
if err := cfg.Load(configFile); err != nil {
|
|
fmt.Printf("Failed to load default configuration: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// Save the config to the installation directory
|
|
if err := cfg.Save(configFile); err != nil {
|
|
fmt.Printf("Failed to save configuration to installation directory: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if sourceConfigFile != "" && sourceConfigFile != configFile {
|
|
fmt.Printf("Configuration copied from %s to %s\n", sourceConfigFile, configFile)
|
|
}
|
|
}
|
|
|
|
switch strings.ToLower(cmd) {
|
|
case "install":
|
|
fmt.Println("Installing service...")
|
|
err = service.InstallService(configFile)
|
|
case "start":
|
|
fmt.Println("Starting service...")
|
|
err = service.StartService()
|
|
case "stop":
|
|
fmt.Println("Stopping service...")
|
|
err = service.StopService()
|
|
case "restart":
|
|
fmt.Println("Restarting service...")
|
|
if err = service.StopService(); err == nil {
|
|
err = service.StartService()
|
|
}
|
|
case "remove":
|
|
fmt.Println("Removing service...")
|
|
err = service.UninstallService()
|
|
case "run":
|
|
fmt.Println("Running service...")
|
|
err = service.RunService(configFile)
|
|
default:
|
|
fmt.Printf("Invalid service command: %s\n", cmd)
|
|
displayHelp()
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err != nil {
|
|
fmt.Printf("Service command failed: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println("Service command completed successfully.")
|
|
}
|
|
|
|
// displayHelp displays help information
|
|
func displayHelp() {
|
|
fmt.Println(`
|
|
User Session Monitor Agent
|
|
|
|
This application monitors Windows session events (logon, logoff, lock, unlock) and sends them to an API.
|
|
It needs to be run with administrative privileges and is designed to be used as a Windows service.
|
|
All configuration values (API key, URL, debug mode, timezone) are read from config.ini.
|
|
|
|
The application includes automatic health checks:
|
|
- On startup: Validates API key and server connectivity
|
|
- Every 30 minutes: Checks server health and retries any failed events
|
|
- After config changes: Verifies new settings work correctly
|
|
|
|
Available command-line options (flags):
|
|
--service <install, start, stop, restart, remove, run>
|
|
Manage the Windows service.
|
|
- install: Installs the service by copying the executable to the installation directory and configuring it.
|
|
- start: Starts the service.
|
|
- stop: Stops the service.
|
|
- restart: Restarts the service (stops and then starts).
|
|
- remove: Removes the service and terminates related processes.
|
|
- run: Runs the service directly (for debugging).
|
|
|
|
--config <path>
|
|
Path to configuration file (used with --service install).
|
|
If specified, this config file will be copied to the installation directory.
|
|
If not specified, looks for config.ini in current directory, otherwise uses defaults.
|
|
Example: --service install --config "c:\users\bob\Downloads\config.ini"
|
|
|
|
--api-key <key>
|
|
Updates the API key in config.ini.
|
|
Example: --api-key 47959c6d5d8db64eb0ec3ad824ccbe82618632e2a58823d84aba92078da693fa
|
|
|
|
--url <url>
|
|
Updates the server URL in config.ini.
|
|
Example: --url https://yourserver:8000
|
|
|
|
--debug <true/false>
|
|
Enables or disables debug logging in config.ini.
|
|
Example: --debug true
|
|
|
|
--timezone <timezone>
|
|
Updates the timezone in config.ini.
|
|
Example: --timezone Europe/London
|
|
|
|
--check-interval <minutes>
|
|
Updates the health check interval in minutes in config.ini.
|
|
Use 0 to disable periodic health checks.
|
|
Example: --check-interval 15
|
|
|
|
--session-log-size <MB>
|
|
Updates the session monitor log rotation size in MB.
|
|
Use 0 to disable rotation, maximum is 20 MB.
|
|
Example: --session-log-size 10
|
|
|
|
--error-log-size <MB>
|
|
Updates the error log rotation size in MB.
|
|
Use 0 to disable rotation, maximum is 20 MB.
|
|
Example: --error-log-size 5
|
|
|
|
--event-log-size <MB>
|
|
Updates the event CSV log rotation size in MB.
|
|
Use 0 to disable rotation, maximum is 20 MB.
|
|
Example: --event-log-size 8
|
|
|
|
After installing this as service, it will copy the exe file to the installation directory and create a default config.
|
|
This will be in:
|
|
C:\ProgramData\UserSessionMon\config.ini
|
|
|
|
The default config looks like below; ensure the correct API key and URL are set.
|
|
[API]
|
|
api_key = 47959c6d5d8db64eb0ec3ad824ccbe82618632e2a58823d84aba92078da693fa
|
|
server_url = https://monitoring.vm.com:8000
|
|
debug_logs = false
|
|
timezone = Europe/London
|
|
health_check_interval = 30
|
|
health_check_path = /api/health
|
|
install_dir = C:\ProgramData\UserSessionMon
|
|
|
|
[Logging]
|
|
session_log_rotation_size_mb = 5
|
|
error_log_rotation_size_mb = 5
|
|
event_log_rotation_size_mb = 5
|
|
|
|
Usage Examples:
|
|
Install the service:
|
|
winagentUSM.exe --service install
|
|
|
|
Start the service:
|
|
winagentUSM.exe --service start
|
|
|
|
Update API key and URL in config.ini:
|
|
winagentUSM.exe --api-key your_api_key --url https://yourserver:8000
|
|
|
|
Enable debug logging:
|
|
winagentUSM.exe --debug true
|
|
|
|
Update timezone:
|
|
winagentUSM.exe --timezone America/New_York
|
|
|
|
Update health check interval to 15 minutes:
|
|
winagentUSM.exe --check-interval 15
|
|
|
|
Disable periodic health checks:
|
|
winagentUSM.exe --check-interval 0
|
|
|
|
Note: Always run this application from an Administrator Command Prompt.
|
|
`)
|
|
}
|