<# .SYNOPSIS Mueve el ratón aleatoriamente solo tras un periodo de inactividad del usuario. .DESCRIPTION Espera a que transcurran TiempoInactividad segundos sin uso de teclado ni ratón y, a partir de entonces, desliza el cursor de forma animada y visible hacia un punto aleatorio cada 2-4 segundos. En cuanto detecta uso real del teclado o del ratón deja de moverlo y vuelve a esperar. Muestra un mensaje con la hora al iniciar y al detener el movimiento. Al arrancar cierra cualquier instancia anterior del script que siga en ejecución. Solo funciona en Windows. .PARAMETER TiempoInactividad Segundos sin actividad antes de empezar a mover el ratón (por defecto 180). .PARAMETER DesplazamientoMin Distancia mínima en píxeles de cada deslizamiento (por defecto 150). .PARAMETER DesplazamientoMax Distancia máxima en píxeles de cada deslizamiento (por defecto 300). .PARAMETER DuracionMs Duración en milisegundos de la animación de cada deslizamiento (por defecto 600). .EXAMPLE .\Mover.ps1 .\Mover.ps1 -TiempoInactividad 60 -DesplazamientoMax 400 #> param( [int]$TiempoInactividad = 180, [int]$DesplazamientoMin = 150, [int]$DesplazamientoMax = 300, [int]$DuracionMs = 600 ) # Intervalo de comprobación de actividad durante las pausas (milisegundos) $IntervaloComprobacionMs = 200 # Número de pasos intermedios de cada animación $PasosAnimacion = 40 <# .SYNOPSIS Cierra las instancias anteriores de este script que sigan en ejecución. .DESCRIPTION Busca procesos de PowerShell cuya línea de comandos ejecute este mismo fichero mediante -File (excluyendo el proceso actual) y los detiene, para evitar que varias instancias se interpreten mutuamente sus movimientos como actividad del usuario. #> function Detener-InstanciasPrevias { $nombre = [regex]::Escape((Split-Path -Leaf $PSCommandPath)) $patron = "-File\s+`"?[^`"]*$nombre" $previas = Get-CimInstance Win32_Process -Filter "Name='powershell.exe' OR Name='pwsh.exe'" | Where-Object { $_.ProcessId -ne $PID -and $_.CommandLine -match $patron } foreach ($proceso in $previas) { Write-Host "[$(Get-Date -Format 'HH:mm:ss')] Cerrando instancia anterior (PID $($proceso.ProcessId))." -ForegroundColor Cyan Stop-Process -Id $proceso.ProcessId -Force -ErrorAction SilentlyContinue } } Detener-InstanciasPrevias Add-Type -AssemblyName System.Windows.Forms, System.Drawing # La compilación del código C# puede tardar varios segundos en el primer arranque Write-Host "[$(Get-Date -Format 'HH:mm:ss')] Inicializando..." Add-Type -TypeDefinition @' using System; using System.Runtime.InteropServices; /// /// Acceso a las APIs de Win32 para consultar la inactividad y mover el ratón. /// public static class Inactividad { [StructLayout(LayoutKind.Sequential)] private struct LASTINPUTINFO { public uint cbSize; public uint dwTime; } [StructLayout(LayoutKind.Sequential)] private struct MOUSEINPUT { public int dx; public int dy; public uint mouseData; public uint dwFlags; public uint time; public IntPtr dwExtraInfo; } [StructLayout(LayoutKind.Sequential)] private struct INPUT { public uint type; public MOUSEINPUT mi; } [StructLayout(LayoutKind.Sequential)] private struct POINT { public int x; public int y; } [DllImport("user32.dll")] private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii); [DllImport("kernel32.dll")] private static extern uint GetTickCount(); [DllImport("user32.dll")] private static extern bool GetCursorPos(out POINT lpPoint); [DllImport("user32.dll")] private static extern int GetSystemMetrics(int nIndex); [DllImport("user32.dll", SetLastError = true)] private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); /// /// Obtiene el instante (ticks del sistema, ms) de la última entrada de teclado o ratón. /// public static uint ObtenerUltimaEntrada() { LASTINPUTINFO info = new LASTINPUTINFO(); info.cbSize = (uint)Marshal.SizeOf(info); if (!GetLastInputInfo(ref info)) throw new System.ComponentModel.Win32Exception(); return info.dwTime; } /// /// Calcula los segundos transcurridos desde la última entrada de teclado o ratón. /// public static double SegundosInactivo() { // La resta sin signo tolera el desbordamiento de GetTickCount (~49,7 días) return unchecked(GetTickCount() - ObtenerUltimaEntrada()) / 1000.0; } /// /// Mueve el puntero a una posición absoluta de pantalla mediante SendInput (entrada real, /// sin verse afectada por la aceleración del puntero). /// /// Coordenada X de destino en el escritorio virtual. /// Coordenada Y de destino en el escritorio virtual. public static void MoverAbsoluto(int x, int y) { int vx = GetSystemMetrics(76), vy = GetSystemMetrics(77); // SM_X/YVIRTUALSCREEN int vw = GetSystemMetrics(78), vh = GetSystemMetrics(79); // SM_CX/CYVIRTUALSCREEN INPUT[] entradas = new INPUT[1]; entradas[0].type = 0; // INPUT_MOUSE // Normalización a 0..65535 sobre el escritorio virtual (todos los monitores) entradas[0].mi.dx = (int)Math.Round((x - vx) * 65535.0 / (vw - 1)); entradas[0].mi.dy = (int)Math.Round((y - vy) * 65535.0 / (vh - 1)); // MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK entradas[0].mi.dwFlags = 0x0001 | 0x8000 | 0x4000; SendInput(1, entradas, Marshal.SizeOf(typeof(INPUT))); } /// /// Obtiene la posición actual del puntero. /// /// Array con las coordenadas {x, y}. public static int[] ObtenerPosicion() { POINT p; GetCursorPos(out p); return new int[] { p.x, p.y }; } /// /// Desliza el puntero de forma animada y visible desde su posición actual hasta el destino. /// /// Coordenada X de destino. /// Coordenada Y de destino. /// Duración total de la animación en milisegundos. /// Número de pasos intermedios de la animación. /// False si el usuario ha movido el ratón durante la animación; true en caso contrario. public static bool Deslizar(int xDestino, int yDestino, int duracionMs, int pasos) { int[] origen = ObtenerPosicion(); int xEsperada = origen[0], yEsperada = origen[1]; for (int i = 1; i <= pasos; i++) { // Si el puntero no está donde lo dejamos, el usuario lo ha movido int[] actual = ObtenerPosicion(); if (Math.Abs(actual[0] - xEsperada) > 2 || Math.Abs(actual[1] - yEsperada) > 2) return false; // Curva de suavizado (aceleración y frenado) para un movimiento natural double t = (double)i / pasos; double s = t * t * (3 - 2 * t); xEsperada = (int)Math.Round(origen[0] + (xDestino - origen[0]) * s); yEsperada = (int)Math.Round(origen[1] + (yDestino - origen[1]) * s); MoverAbsoluto(xEsperada, yEsperada); System.Threading.Thread.Sleep(duracionMs / pasos); } return true; } } '@ <# .SYNOPSIS Bloquea hasta que el usuario lleve TiempoInactividad segundos sin usar teclado ni ratón. #> function Esperar-Inactividad { while ([Inactividad]::SegundosInactivo() -lt $TiempoInactividad) { Start-Sleep -Seconds 1 } } <# .SYNOPSIS Calcula un destino aleatorio para el puntero dentro del monitor en el que se encuentra. .DESCRIPTION Elige una dirección aleatoria y una distancia entre DesplazamientoMin y DesplazamientoMax. Si el destino queda fuera del monitor, se invierte la dirección y, en último caso, se limita a sus bordes para no perder el puntero entre pantallas. .OUTPUTS Array con las coordenadas {x, y} de destino. #> function Obtener-DestinoAleatorio { $pos = [Inactividad]::ObtenerPosicion() $limites = [System.Windows.Forms.Screen]::FromPoint((New-Object System.Drawing.Point($pos[0], $pos[1]))).Bounds $angulo = (Get-Random -Minimum 0.0 -Maximum 1.0) * 2 * [Math]::PI $distancia = Get-Random -Minimum $DesplazamientoMin -Maximum ($DesplazamientoMax + 1) $dx = [int]($distancia * [Math]::Cos($angulo)) $dy = [int]($distancia * [Math]::Sin($angulo)) # Invierte el sentido en el eje que se salga del monitor if ($pos[0] + $dx -lt $limites.Left -or $pos[0] + $dx -ge $limites.Right) { $dx = -$dx } if ($pos[1] + $dy -lt $limites.Top -or $pos[1] + $dy -ge $limites.Bottom) { $dy = -$dy } $x = [Math]::Min([Math]::Max($pos[0] + $dx, $limites.Left + 5), $limites.Right - 6) $y = [Math]::Min([Math]::Max($pos[1] + $dy, $limites.Top + 5), $limites.Bottom - 6) return @($x, $y) } <# .SYNOPSIS Mueve el ratón aleatoriamente hasta detectar actividad real del usuario. .DESCRIPTION Desliza el puntero de forma animada para que el desplazamiento sea visible. Los movimientos propios también actualizan GetLastInputInfo, por lo que tras cada deslizamiento se guarda el instante resultante; cualquier cambio posterior indica uso real del usuario. Si el usuario mueve el ratón durante la animación, ésta se interrumpe. #> function Mover-MientrasInactivo { while ($true) { $destino = Obtener-DestinoAleatorio if (-not [Inactividad]::Deslizar($destino[0], $destino[1], $DuracionMs, $PasosAnimacion)) { return } Start-Sleep -Milliseconds 50 $ultimaPropia = [Inactividad]::ObtenerUltimaEntrada() $finPausa = (Get-Date).AddSeconds((Get-Random -Minimum 2 -Maximum 5)) while ((Get-Date) -lt $finPausa) { Start-Sleep -Milliseconds $IntervaloComprobacionMs if ([Inactividad]::ObtenerUltimaEntrada() -ne $ultimaPropia) { return } } } } Write-Host "[$(Get-Date -Format 'HH:mm:ss')] Mover iniciado. Esperando $TiempoInactividad s de inactividad (Ctrl+C para salir)." # Bucle principal: espera la inactividad, mueve el ratón y se detiene al detectar uso while ($true) { try { Esperar-Inactividad Write-Host "[$(Get-Date -Format 'HH:mm:ss')] Inactividad detectada: iniciando movimiento del ratón." -ForegroundColor Yellow Mover-MientrasInactivo Write-Host "[$(Get-Date -Format 'HH:mm:ss')] Actividad del usuario detectada: movimiento detenido." -ForegroundColor Green } catch { Start-Sleep -Seconds 1 } }