Skip to content

Instantly share code, notes, and snippets.

@joshooaj
Last active January 17, 2026 23:38
Show Gist options
  • Select an option

  • Save joshooaj/a35f99c10c028faf97086a6f31139a00 to your computer and use it in GitHub Desktop.

Select an option

Save joshooaj/a35f99c10c028faf97086a6f31139a00 to your computer and use it in GitHub Desktop.
A little PowerShell module for moving, clicking, and scrolling the mouse on Windows

PSMouseControl

A PowerShell module for Windows that provides programmatic control over mouse position, clicks, scrolling, and smooth cursor movement with natural-looking bezier curves.

Origin Story

This module started as a simple script to monitor my daughter's computer usage. I wanted to know when she'd been on the computer too long so I could generate a notification. But once I started playing with P/Invoke through PowerShell, I couldn't resist implementing more functionality.

Requirements

  • OS: Windows
  • PowerShell: 5.1 or 7+

Installation

  1. Copy PSMouseControl.psm1 to your PowerShell modules directory, or
  2. Import the module directly:
    Import-Module .\PSMouseControl.psm1

Features

  • Get/Set mouse position with pixel-perfect accuracy
  • Click left, right, or middle mouse buttons
  • Scroll vertically and horizontally
  • Plot smooth paths between points with natural-looking bezier curves
  • Natural mouse movement with automatic acceleration/deceleration
  • Monitor mouse position in real-time

Functions

Get-MousePosition

Gets the current mouse cursor position.

$pos = Get-MousePosition
Write-Host "X: $($pos.X), Y: $($pos.Y)"

Set-MousePosition

Sets the mouse cursor position to specific coordinates.

Set-MousePosition -X 500 -Y 300

# Or use pipeline input
New-MousePosition 500 300 | Set-MousePosition

New-MousePosition

Creates a new PSMouseControl.Point object representing screen coordinates.

$point = New-MousePosition -X 100 -Y 200

New-MousePath

Generates a smooth, natural-looking path between two points using bezier curves with automatic acceleration and deceleration. The control point is automatically generated with a random perpendicular offset from the direct path.

$start = New-MousePosition 100 700
$end = New-MousePosition 2000 125

# Move the mouse smoothly along the path
New-MousePath -Start $start -End $end | ForEach-Object {
    $_ | Set-MousePosition
    Start-Sleep -Milliseconds 10
}

Features:

  • Automatic control point generation for natural curves
  • Ease-in/ease-out (cubic) animation for smooth acceleration/deceleration
  • Random curve variation to avoid repetitive patterns

Invoke-MouseClick

Simulates a mouse button click.

# Left click (default)
Invoke-MouseClick

# Right click
Invoke-MouseClick -Button Right

# Middle click
Invoke-MouseClick -Button Middle

Valid buttons: Left, Right, Middle

Invoke-MouseScroll

Simulates mouse wheel scrolling in any direction.

# Scroll up
Invoke-MouseScroll -Direction Up

# Scroll down
Invoke-MouseScroll -Direction Down

# Horizontal scroll left
Invoke-MouseScroll -Direction Left

# Horizontal scroll right
Invoke-MouseScroll -Direction Right

Valid directions: Up, Down, Left, Right

Watch-MousePosition

Continuously monitors and outputs the mouse position. Stops when the mouse reaches coordinates (0, 0).

# Monitor with default 10ms interval
Watch-MousePosition

# Monitor with custom interval
Watch-MousePosition -Interval ([timespan]::FromMilliseconds(100))

# With verbose output
Watch-MousePosition -Verbose

Usage Examples

Move mouse in a natural arc between two points

Import-Module .\PSMouseControl.psm1

$start = Get-MousePosition
$end = New-MousePosition 1920 1080

New-MousePath -Start $start -End $end | ForEach-Object {
    Set-MousePosition -X $_.X -Y $_.Y
    Start-Sleep -Milliseconds 10
}

Click at a specific location

Set-MousePosition -X 500 -Y 300
Invoke-MouseClick -Button Left

Monitor mouse position until top-left corner

Watch-MousePosition | ForEach-Object {
    Write-Host "Current position: $($_.X), $($_.Y)"
}

Automated interaction sequence

# Move to button location
$buttonPos = New-MousePosition 640 480
New-MousePath -Start (Get-MousePosition) -End $buttonPos | ForEach-Object {
    $_ | Set-MousePosition
    Start-Sleep -Milliseconds 8
}

# Click the button
Invoke-MouseClick -Button Left
Start-Sleep -Seconds 1

# Scroll down to see more content
Invoke-MouseScroll -Direction Down

Technical Details

The module uses P/Invoke to call Windows User32.dll functions:

  • GetCursorPos - Get cursor position
  • SetCursorPos - Set cursor position
  • mouse_event - Simulate mouse clicks and scrolling
  • SetProcessDPIAware - Ensure accurate positioning on high-DPI displays

Natural Mouse Movement Algorithm

The New-MousePath function creates natural-looking mouse movements using:

  1. Quadratic Bezier Curves: Generates a curved path instead of straight lines
  2. Automatic Control Point: Creates a perpendicular offset (10-25% of distance) from the direct path
  3. Ease-In/Ease-Out: Cubic easing function makes the cursor accelerate smoothly at the start and decelerate at the end
  4. Randomization: Each path varies slightly to avoid robotic patterns
Import-Module ./PSMouseControl.psm1
Write-Host -ForegroundColor Green "Mouse movements are random`nCTRL+C to stop"
while ($true) {
$start = Get-MousePosition
$end = [PSMouseControl.Point]@{
X = Get-Random -Min 0 -Max 2560
Y = Get-Random -Min 0 -Max 1440
}
Plot-MousePath $start $end | ForEach-Object {
$_ | Set-MousePosition
Start-Sleep -Milliseconds 1
}
}
$typeDef = @'
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace PSMouseControl
{
struct FIXED
{
public short fract;
public short value;
}
struct POINT
{
public FIXED x;
public FIXED y;
}
public enum Button
{
Left,
Right,
Middle
}
public enum Direction
{
Up,
Down,
Left,
Right,
}
public struct Point
{
public int X;
public int Y;
}
public static class Mouse
{
const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
const uint MOUSEEVENTF_LEFTUP = 0x0004;
const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;
const uint MOUSEEVENTF_RIGHTUP = 0x0010;
const uint MOUSEEVENTF_MIDDLEDOWN = 0x0020;
const uint MOUSEEVENTF_MIDDLEUP = 0x0040;
const uint MOUSEEVENTF_WHEEL = 0x0800;
const uint MOUSEEVENTF_HWHEEL = 0x01000;
[DllImport("user32.dll")]
private static extern void mouse_event(uint dwFlags, uint dx, uint dy, int dwData, UIntPtr dwExtraInfo);
[DllImport("user32.dll")]
static extern bool GetCursorPos(ref POINT lpPoint);
[DllImport("user32.dll", SetLastError=true)]
static extern bool SetProcessDPIAware();
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetCursorPos(int x, int y);
static Mouse()
{
SetProcessDPIAware();
}
public static Point Position
{
get
{
var pt = new POINT();
GetCursorPos(ref pt);
return new Point()
{
X = pt.x.fract,
Y = pt.y.fract,
};
}
set
{
SetCursorPos(value.X, value.Y);
}
}
public static void Click(Button button)
{
var buttonDown = MOUSEEVENTF_LEFTDOWN;
var buttonUp = MOUSEEVENTF_LEFTUP;
switch (button)
{
case Button.Right:
buttonDown = MOUSEEVENTF_RIGHTDOWN;
buttonUp = MOUSEEVENTF_RIGHTUP;
break;
case Button.Middle:
buttonDown = MOUSEEVENTF_MIDDLEDOWN;
buttonUp = MOUSEEVENTF_MIDDLEUP;
break;
}
mouse_event(buttonDown, 0, 0, 0, UIntPtr.Zero);
Thread.Sleep(100);
mouse_event(buttonUp, 0, 0, 0, UIntPtr.Zero);
}
public static void Scroll(Direction direction)
{
var wheel = MOUSEEVENTF_WHEEL;
var delta = 1;
switch (direction)
{
case Direction.Up:
break;
case Direction.Down:
delta = -1;
break;
case Direction.Left:
wheel = MOUSEEVENTF_HWHEEL;
delta = -1;
break;
case Direction.Right:
wheel = MOUSEEVENTF_HWHEEL;
break;
}
for (var i = 0; i < 100; i++)
{
mouse_event(wheel, 0, 0, delta, UIntPtr.Zero);
}
}
}
}
'@
if ($null -eq ('PSMouseControl.Mouse' -as [type])) {
Add-Type -TypeDefinition $typeDef
}
function Get-MousePosition {
[CmdletBinding()]
[OutputType([PSMouseControl.Point])]
param ()
[PSMouseControl.Mouse]::Position
}
function Set-MousePosition {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[int]
$X,
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[int]
$Y
)
[PSMouseControl.Mouse]::Position = [PSMouseControl.Point]@{X = $X; Y = $Y }
}
function New-MousePosition {
[CmdletBinding()]
[OutputType([PSMouseControl.Point])]
param(
[Parameter(Mandatory)]
[int]
$X,
[Parameter(Mandatory)]
[int]
$Y
)
[PSMouseControl.Point]@{
X = $X
Y = $Y
}
}
function New-MousePath {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[PSMouseControl.Point]
$Start,
[Parameter(Mandatory)]
[PSMouseControl.Point]
$End
)
$middle = Get-BezierControlPoint $Start $End
for ($i = 0; $i -lt 1; $i += 0.01) {
# Apply easing for natural acceleration/deceleration
$t = EaseInOutCubic $i
$xa = Interpolate $Start.X $middle.X $t
$ya = Interpolate $Start.Y $middle.Y $t
$xb = Interpolate $middle.X $End.X $t
$yb = Interpolate $middle.Y $End.Y $t
$x = Interpolate $xa $xb $t
$y = Interpolate $ya $yb $t
New-MousePosition $x $y
}
}
function Invoke-MouseClick {
[CmdletBinding()]
param(
[Parameter(Position = 0)]
[ValidateSet('Left', 'Right', 'Middle')]
[string]
$Button
)
[PSMouseControl.Mouse]::Click($Button)
}
function Invoke-MouseScroll {
[CmdletBinding()]
param(
[Parameter(Position = 0)]
[ValidateSet('Up', 'Down', 'Left', 'Right')]
[string]
$Direction
)
[PSMouseControl.Mouse]::Scroll($Direction)
}
function Watch-MousePosition {
[CmdletBinding()]
param(
[Parameter()]
[timespan]
$Interval = [timespan]::FromSeconds(1)
)
process {
Write-Host "Capturing the mouse position every $($Interval.TotalSeconds.ToString('n2')) seconds."
Write-Host "Press any key to stop. . ."
do {
$pos = Get-MousePosition
Write-Verbose "Mouse Position: $($pos.X), $($pos.Y)"
$pos
if ([console]::KeyAvailable) {
break;
}
Start-Sleep -Milliseconds $Interval.TotalMilliseconds
} while ($true)
while ([console]::KeyAvailable) {
$null = [console]::ReadKey($true)
}
}
}
function Interpolate([int]$from, [int]$to, [float]$percent) {
$difference = $to - $from
$from + $difference * $percent
}
function EaseInOutCubic([float]$t) {
# Easing function for smooth acceleration and deceleration
if ($t -lt 0.5) {
return 4 * $t * $t * $t
} else {
$p = -2 * $t + 2
return 1 - ($p * $p * $p) / 2
}
}
function Get-BezierControlPoint($start, $end) {
# Calculate a natural-looking control point between start and end
# This creates a slight curve by offsetting perpendicular to the direct path
$dx = $end.X - $start.X
$dy = $end.Y - $start.Y
$distance = [Math]::Sqrt($dx * $dx + $dy * $dy)
# Midpoint between start and end
$midX = ($start.X + $end.X) / 2
$midY = ($start.Y + $end.Y) / 2
# Perpendicular offset (rotated 90 degrees)
# Use 10-25% of the distance as the offset, with some randomness
$offsetPercent = (Get-Random -Min 10 -Max 25) / 100.0
$offsetDistance = $distance * $offsetPercent
# Randomly choose left or right side
$direction = if ((Get-Random -Min 0 -Max 2) -eq 0) { 1 } else { -1 }
# Calculate perpendicular vector (swap x/y and negate one)
if ($distance -gt 0) {
$perpX = - $dy / $distance * $offsetDistance * $direction
$perpY = $dx / $distance * $offsetDistance * $direction
} else {
$perpX = 0
$perpY = 0
}
New-MousePosition ($midX + $perpX) ($midY + $perpY)
}
Export-ModuleMember -Function Get-MousePosition, Set-MousePosition, New-MousePosition, New-MousePath, Invoke-MouseClick, Invoke-MouseScroll, Watch-MousePosition
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment