├── Config.ps1 ├── Doc ├── Main Doc.docx └── Setting up VM network in VirtualBox for user simulation.docx ├── EnableEverything.ps1 ├── Misc ├── to_write.txt └── written.txt ├── Modules ├── IE.ps1 ├── MapShare.ps1 ├── Outlook.ps1 ├── Tools.ps1 └── Type.ps1 ├── README.md ├── RunUserSimulation.ps1 └── schedule.txt /Config.ps1: -------------------------------------------------------------------------------- 1 | #Hey! If you want to change some parameters you are at the right place! 2 | #You can ONLY change the values of the variables, not their names!! 3 | #You also MUST change values by values of the same type: eg you can change "$IEdepth = 4" to "$IEdepth = 3" but NOT to "$IEdepth = 'auto' " 4 | 5 | 6 | 7 | 8 | ################################################################################################################## 9 | #######################################################[GLOBAL CONFIG]##################################################### 10 | #parameters for the main programm 11 | 12 | #what to simulate ($false / $true) 13 | 14 | $global_IE = $true #whether to simulate an user internet explorer activity 15 | $global_MapShare = $false #whether to attempt to map shared drives to 'K' !Warning! This can expose the machine to attacks 16 | 17 | $global_type = $false #whether to simulate keystrokes (copying the contents of file .\Misc\to_write.txt) 18 | #!Beware of this feature! If another script is running, or even just an user clicking somewhere, this could shut down the virtual machine or launch programms via keystrokes 19 | 20 | 21 | 22 | 23 | 24 | $global_unactivity = 1 #time (seconds) the user will do nothing, before repeating a range of different activities. Value between 1 and 3600 25 | $global_actiontimeout = 60 #max time in seconds by activity, if something takes longer it will be terminated. Value between 1 and 3600 26 | $global_duration = 0 #how long run the simulation? Specify the duration in minutes from 1 to 1000000000, with 0 indicating infinite. Remember it has to complete a round of activities before stopping 27 | #so if some activity takes long it will not stop exactly after the specified time. 28 | 29 | 30 | 31 | 32 | 33 | 34 | $global_standalone = $true #whether to operate only localy ($true) or server-mode ($false) 35 | 36 | 37 | 38 | 39 | 40 | 41 | $global_schedule = $false #whether to follow the given schedule 42 | 43 | 44 | 45 | 46 | $global_email = $false #whether to send an email with the latest log to the specified address at the end of the simulation (if it's ending). Your server must support anonymous sending (rarely the case...). 47 | 48 | 49 | ################################################################################################################## 50 | ################################################################################################################## 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | ################################################################################################################## 64 | #######################################################[Server Mode]##################################################### 65 | #parameters for the server mode function (if active) 66 | $global_autodetect = $true # whether to auto-detect PC running on the same network. Beware! : it relies on microsoft 'net -view' command and windows discovery of network PC. It is unreliable, even more if the 67 | #instructions given in documentation are not followed 68 | 69 | $global_listPCs = "PC1","PC2" #Has no effect if autodetect is $true. But can be useful if one already know the what the network is made of (read: the names of PCs in the network) 70 | # IF you already know the names of the PCs in the network and are sure they are connected etc... PLEASE set autodetect to false and use this list, it is much more reliable 71 | 72 | 73 | 74 | ################################################################################################################## 75 | ################################################################################################################## 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | ################################################################################################################## 92 | #######################################################[IE]##################################################### 93 | #parameters for the internet explorer usage simulation 94 | 95 | $IE_quitafter = 3 #quitafter : How much time to wait before closing a webpage. default 3 (seconds), value between 1 and 10000000000 96 | 97 | $IE_depth = 5 #depth: IE simulation browse a website from the list, click a link found on the page etc.... 98 | #until it reach depth. value between 1 and 1000000 . Note: the bigger the depth, the higher the chances of browsing being reported as failure (more chances to click a invalid link or timeout) 99 | 100 | 101 | 102 | $IE_URIs = "https://news.google.com", 103 | "https://www.reddit.com", 104 | "https://www.msn.com", 105 | "http://www.cnn.com", 106 | "http://www.bbc.com", 107 | "http://unvalidaddressthisdonoexist.ddns.net" #list of urls to try to visit for IE simulation 108 | 109 | ################################################################################################################## 110 | ################################################################################################################## 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | ################################################################################################################## 128 | ##################################################[MapShare]######################################################### 129 | #parameters for the shared drive maping (LLMNR traffic generator) 130 | 131 | 132 | 133 | $MapShare_locations = "\\sharepointofjohnsmith.microsoft\share", 134 | "\\MySharedDrivs\share" #list of (fakes?) sharepoints the user try to connect to and map their K drive to. 135 | 136 | 137 | 138 | #################################################################################################################### 139 | ################################################################################################################## 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | ##################################################[MapShare]######################################################### 152 | #parameters for the shared drive maping (LLMNR traffic generator) 153 | 154 | #the text to be typed can be found (and modified) in Misc\to_write.txt 155 | 156 | 157 | $Type_speed = 60 #time taken to write one character, in milliseconds (lower value means faster typing) Value between 1 and 10000000 . Anything too big is unadvised as for long text it would take a while and get timeout-ed 158 | 159 | 160 | 161 | #################################################################################################################### 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | ##################################################[Mail logs]######################################################### 174 | #how and to whom send logs. !this could not be tested. IF YOU ARE UNSURE DO NOT MODIFY ANTYHING 175 | 176 | $global:PSEmailServer = "smtp.domain.com" #if you wish to get logs via e-mail and activated the global_email option, you must provide a valid SMTP server. The server also must support anonymous sender (which is rarely the case..) 177 | $global:email_address = "name@domain.com" #The email address to who send the logs. If global_email is activated, you have to provide a valid email. 178 | 179 | 180 | 181 | ################################################################################################################## 182 | 183 | -------------------------------------------------------------------------------- /Doc/Main Doc.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/simple-user-simulation/3fb568b03371dbadffe4f578bb78eb20ed2319d1/Doc/Main Doc.docx -------------------------------------------------------------------------------- /Doc/Setting up VM network in VirtualBox for user simulation.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelb/simple-user-simulation/3fb568b03371dbadffe4f578bb78eb20ed2319d1/Doc/Setting up VM network in VirtualBox for user simulation.docx -------------------------------------------------------------------------------- /EnableEverything.ps1: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | function Use-RunAs 5 | { 6 | # Check if script is running as Adminstrator and if not use RunAs 7 | # Use Check Switch to check if admin 8 | 9 | param([Switch]$Check) 10 | 11 | $IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()` 12 | ).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") 13 | 14 | if ($Check) { return $IsAdmin } 15 | 16 | if ($MyInvocation.ScriptName -ne "") 17 | { 18 | if (-not $IsAdmin) 19 | { 20 | try 21 | { 22 | $arg = "-file `"$($MyInvocation.ScriptName)`"" 23 | Start-Process "$psHome\powershell.exe" -Verb Runas -ArgumentList $arg -ErrorAction 'stop' 24 | } 25 | catch 26 | { 27 | Write-Warning "Error - Failed to restart script with runas" 28 | break 29 | } 30 | exit # Quit this session of powershell 31 | } 32 | } 33 | else 34 | { 35 | Write-Warning "Error - Script must be saved as a .ps1 file first" 36 | break 37 | } 38 | } 39 | 40 | 41 | 42 | 43 | #ask for admin rights 44 | Use-RunAs 45 | "Running as administrator" 46 | 47 | 48 | "" 49 | "" 50 | "Please answer Y (yes) to the following question" 51 | "" 52 | "" 53 | 54 | # Set start mode to automatic 55 | Set-Service WinRM -StartMode Automatic -ErrorAction SilentlyContinue 56 | 57 | "Step 1 done" 58 | 59 | # Verify start mode and state - it should be running 60 | #Get-WmiObject -Class win32_service | Where-Object {$_.name -like "WinRM"} 61 | 62 | 63 | # Trust all hosts 64 | Set-Item WSMan:localhost\client\trustedhosts -value * -ErrorAction SilentlyContinue 65 | 66 | "" 67 | "Step 2 done" 68 | 69 | # Verify trusted hosts configuration 70 | #Get-Item WSMan:\localhost\Client\TrustedHosts 71 | "" 72 | 73 | 74 | Try { 75 | Enable-PSRemoting –force 76 | }Catch { 77 | "An error occured while trying to enable powershell remoting." 78 | "It may happen when it was already activated, or else you are in trouble" 79 | } 80 | 81 | "" 82 | "" 83 | "All set!" 84 | "This will close automatically now" 85 | Start-Sleep -s 5 86 | exit 87 | -------------------------------------------------------------------------------- /Misc/to_write.txt: -------------------------------------------------------------------------------- 1 | Hello,My name is John Smith 2 | I believe my PC is totally safe and no one is keylogging me 3 | 4 | So I may take note of my gmail password just here: johnsmith1985 It's my surname and name plus my birth year 5 | 6 | 7 | 8 | You are welcome! 9 | 10 | 11 | Btw if you want to modify this file, please take care to not include special characters as they may invoke special keystrokes 12 | 13 | 14 | Following is a little lorem ipsum 15 | 16 | 17 | 18 | 19 | Lorem ipsum dolor sit amet. -------------------------------------------------------------------------------- /Misc/written.txt: -------------------------------------------------------------------------------- 1 | Hell,My nameis JohnSmit I belive my PC is totaly saf andno oneis keylgging me So I maytak not of my gmal pasword jus her: johnmith1985 It' my surame andnam plu my birh yea Youarewelome! Btwifyo wan t mdify tis fle, pease tke cre t nt iclude secial caracters a tey my ivoke secial kystrokes Fllowing i alttle lrem isum Lrem isum dlor st amet. -------------------------------------------------------------------------------- /Modules/IE.ps1: -------------------------------------------------------------------------------- 1 | Set-Location $args[0] 2 | 3 | 4 | 5 | Try{. .\Config.ps1 -ErrorAction Stop} 6 | Catch { 7 | Write-Host Config File could not ne loaded. Looks like you removed the config file, left it open, or that you made a error when ediditng it. 8 | Write-Host This program will exit in one minute 9 | Start-Sleep -s 60 10 | Break 11 | } 12 | 13 | 14 | 15 | 16 | 17 | 18 | function simulateIE 19 | { 20 | [CmdletBinding()] 21 | Param( 22 | [Parameter(Mandatory=$False)] 23 | [int]$quitafter=3, 24 | [Parameter(Mandatory=$False)] 25 | [int]$depth=3 26 | ) 27 | 28 | Try { 29 | 30 | 31 | 32 | $url2 = get_url 33 | browse -url $url2 -depth $depth -quitafter $quitafter -ErrorAction Stop 34 | 35 | } 36 | Catch { 37 | 38 | "Result: Something failed with IE simulation" 39 | } 40 | if(!$Error){"Result: IE simulation successfully completed"} 41 | 42 | #close everything 43 | $temp = (Get-Process iexplore -ErrorAction SilentlyContinue | Stop-Process) 44 | $temp = (Get-Process ielowutil -ErrorAction SilentlyContinue | Stop-Process) 45 | 46 | } 47 | 48 | 49 | 50 | 51 | 52 | function browse 53 | { 54 | 55 | # open internet explorer and follow links found on the page up to provided depth 56 | [CmdletBinding()] 57 | Param ([string]$url, 58 | [int]$depth, 59 | [int]$quitafter) 60 | 61 | $IE = New-Object -com internetexplorer.application 62 | $IE.visible = $true 63 | 64 | 65 | $asm = [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 66 | $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds 67 | $ie.Width = $screen.width 68 | $ie.Height =$screen.height 69 | $ie.Top = 0 70 | $ie.Left = 0 71 | 72 | 73 | 74 | $IE.navigate2($url) 75 | $IE.visible = $true 76 | Start-Sleep -s $quitafter 77 | 78 | 79 | while ($depth -gt 0){ 80 | 81 | 82 | 83 | $hsg = Invoke-WebRequest -Uri ($url) -UseBasicParsing 84 | 85 | #get all valid links in the page 86 | $links = $hsg.Links.Href | Sort-Object | Get-Unique | select-string -pattern http 87 | $nblink = ($links).count 88 | 89 | #if there are links in the page 90 | if ($nblink -gt 0) { 91 | 92 | 93 | #prepare next link and depth for recursive call 94 | $url = $links[(Get-Random -Maximum ([array]$links).count)] 95 | 96 | 97 | 98 | $IE.navigate2($url) 99 | $IE.visible = $true 100 | Start-Sleep -s $quitafter 101 | 102 | $depth = $depth - 1 103 | 104 | } 105 | } 106 | 107 | 108 | Start-Sleep -s 2 109 | 110 | $IE.quit() 111 | 112 | } 113 | 114 | 115 | 116 | 117 | function get_url 118 | { 119 | 120 | $url = $IE_URIs[(Get-Random -Maximum ([array]$IE_URIs).count)] 121 | return $url 122 | } 123 | 124 | simulateIE -quitafter $IE_quitafter -depth $IE_depth -------------------------------------------------------------------------------- /Modules/MapShare.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 3.0 2 | Set-Location $args[0] 3 | 4 | 5 | Try{. .\Config.ps1 -ErrorAction Stop} 6 | Catch { 7 | Write-Host Config File could not ne loaded. Looks like you removed the config file, left it open, or that you made a error when ediditng it. 8 | Write-Host This program will exit in one minute 9 | Start-Sleep -s 60 10 | Break 11 | } 12 | 13 | 14 | 15 | function mapdrive #warning : this could expose the machine to a real attack 16 | { 17 | #$MapShare_locations = "\\sharepointofjohnsmith.microsoft", "\\MySharedDrivs", "\\catchmeifyoukhan.net" #this figures in the config file 18 | 19 | 20 | 21 | 22 | try { 23 | "Trying to connect to shared drive and map it to drive 'K'" 24 | $sharelocation = $MapShare_locations[(Get-Random -Maximum ([array]$MapShare_locations).count)] 25 | New-PSDrive -Name "K" -PSProvider FileSystem -Root $sharelocation -ErrorAction Stop 26 | } 27 | catch { 28 | "Failure attempting to connect to shared drive. You may have (purposely?) provided a wrong sharepoint network location" 29 | } 30 | 31 | if (!$Error){"Result: Map Shares simulation completed successfully"} 32 | 33 | 34 | } 35 | 36 | mapdrive -------------------------------------------------------------------------------- /Modules/Outlook.ps1: -------------------------------------------------------------------------------- 1 |  2 | function Invoke-UserSimulator { 3 | 4 | <# 5 | 6 | .SYNOPSIS 7 | 8 | 9 | 10 | Simulates common user behaviour on local and remote Windows hosts. 11 | 12 | Authors: Barrett Adams (@peewpw) and Chris Myers (@swizzlez_) 13 | 14 | 15 | 16 | .DESCRIPTION 17 | 18 | 19 | 20 | Performs different actions to simulate real user activity and is intended for use in a lab 21 | 22 | environment. It will browse the internet using Internet Explorer, attempt to map non-existant 23 | 24 | network shares, and open emails with Outlook, including embeded links and attachments. 25 | 26 | 27 | 28 | .PARAMETER Standalone 29 | 30 | 31 | 32 | Define if the script should run as a standalone script on the localhost or on remote systems. 33 | 34 | 35 | 36 | .PARAMETER ConfigXML 37 | 38 | 39 | 40 | The configuration xml file to use when running on remote hosts. 41 | 42 | 43 | 44 | .PARAMETER IE 45 | 46 | 47 | 48 | Run the Internet Explorer simulation. 49 | 50 | 51 | 52 | .PARAMETER Shares 53 | 54 | 55 | 56 | Run the mapping shares simulation. 57 | 58 | 59 | 60 | .PARAMETER Email 61 | 62 | 63 | 64 | Run the opening email simulation. 65 | 66 | 67 | 68 | .PARAMETER All 69 | 70 | 71 | 72 | Run all script simulation functions (IE, Shares, Email). 73 | 74 | 75 | 76 | .EXAMPLE 77 | 78 | 79 | 80 | Import the script modules: 81 | 82 | PS>Import-Module .\Invoke-UserSimulator.ps1 83 | 84 | 85 | 86 | Run only the Internet Explorer function on the local host: 87 | 88 | PS>Invoke-UserSimulator -StandAlone -IE 89 | 90 | 91 | 92 | Configure remote hosts prior to running the script remotely: 93 | 94 | PS>Invoke-ConfigureHosts -ConfigXML .\config.xml 95 | 96 | 97 | 98 | Run all simulation functionality on remote hosts configured in the config.xml file: 99 | 100 | PS>Invoke-UserSimulator -ConfigXML .\config.xml -All 101 | 102 | 103 | 104 | #> 105 | 106 | [CmdletBinding()] 107 | 108 | Param( 109 | 110 | [Parameter(Mandatory=$False)] 111 | 112 | [switch]$StandAlone, 113 | 114 | 115 | 116 | [Parameter(Mandatory=$False)] 117 | 118 | [switch]$Email, 119 | 120 | 121 | 122 | [Parameter(Mandatory=$False)] 123 | 124 | [switch]$IE, 125 | 126 | 127 | 128 | [Parameter(Mandatory=$False)] 129 | 130 | [switch]$Shares, 131 | 132 | 133 | 134 | [Parameter(Mandatory=$False)] 135 | 136 | [switch]$All, 137 | 138 | 139 | 140 | [Parameter(Mandatory=$False)] 141 | 142 | [string]$ConfigXML 143 | 144 | ) 145 | 146 | 147 | 148 | $RemoteScriptBlock = { 149 | 150 | # TOOL FUNCTIONS 151 | 152 | 153 | 154 | [CmdletBinding()] 155 | 156 | Param( 157 | 158 | [Parameter(Position = 0, Mandatory = $false)] 159 | 160 | [Int]$EmailInterval, 161 | 162 | 163 | 164 | [Parameter(Position = 1, Mandatory = $false)] 165 | 166 | [Int]$PageDuration, 167 | 168 | 169 | 170 | [Parameter(Position = 2, Mandatory = $false)] 171 | 172 | [Int]$LinkDepth, 173 | 174 | 175 | 176 | [Parameter(Position = 3, Mandatory = $false)] 177 | 178 | [Int]$MountInterval, 179 | 180 | 181 | 182 | [Parameter(Position = 4, Mandatory=$False)] 183 | 184 | [Int]$Email, 185 | 186 | 187 | 188 | [Parameter(Position = 5, Mandatory=$False)] 189 | 190 | [Int]$IE, 191 | 192 | 193 | 194 | [Parameter(Position = 6, Mandatory=$False)] 195 | 196 | [Int]$Shares, 197 | 198 | 199 | 200 | [Parameter(Position = 7, Mandatory=$False)] 201 | 202 | [Int]$All 203 | 204 | ) 205 | 206 | 207 | 208 | # Creates an Outlook COM Object, then iterates through unread mail. Parses the mail 209 | 210 | # and opens links in IE. Downloads and executes any attachments in a Microsoft trusted 211 | 212 | # folder, resulting in automatic MACRO execution. 213 | 214 | $InvokeOpenEmail = { 215 | 216 | Param( 217 | 218 | [Parameter(Position = 0, Mandatory=$True)] 219 | 220 | [int]$Interval 221 | 222 | ) 223 | 224 | 225 | 226 | While ($True) { 227 | 228 | 229 | 230 | Add-type -assembly "Microsoft.Office.Interop.Outlook" 231 | 232 | $Outlook = New-Object -comobject Outlook.Application 233 | 234 | $Outlook.visible = $True 235 | 236 | $namespace = $Outlook.GetNameSpace("MAPI") 237 | 238 | $namespace.Logon("","",$false,$true) 239 | 240 | $namespace.SendAndReceive($false) 241 | 242 | Start-Sleep -s 60 243 | 244 | $inbox = $namespace.GetDefaultFolder([Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderInbox) 245 | 246 | $filepath = ("C:\Users\" + $env:username + "\AppData\Roaming\Microsoft\Templates") 247 | 248 | 249 | 250 | ForEach($mail in $inbox.Items.Restrict("[Unread] = True")) { 251 | 252 | Write-Host "Found Emails" 253 | 254 | If ($mail.Attachments) { 255 | 256 | Write-Host "Found attachments!!" 257 | 258 | ForEach ($attach in $mail.Attachments) { 259 | 260 | Write-Host "in attachment loop..." 261 | 262 | $path = (Join-Path $filepath $attach.filename) 263 | 264 | $attach.saveasfile($path) 265 | 266 | Invoke-Item $path 267 | 268 | } 269 | 270 | } 271 | 272 | 273 | 274 | # URL Regex 275 | 276 | [regex]$regex = "([a-zA-Z]{3,})://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)*?" 277 | 278 | $URLs = echo $mail.Body | Select-String -Pattern $regex -AllMatches 279 | 280 | 281 | 282 | ForEach ($object in $URLs.Matches) { 283 | 284 | $ie = new-object -COM "InternetExplorer.Application" 285 | 286 | $ie.visible=$true 287 | 288 | echo "Parsed link:" $object.Value 289 | 290 | $ie.navigate2($object.Value) 291 | 292 | $timeout = 0 293 | 294 | While ($ie.busy -And $timeout -lt 10) { 295 | 296 | Start-Sleep -milliseconds 1000 297 | 298 | $timeout += 1 299 | 300 | } 301 | 302 | Start-Sleep -s 15 303 | 304 | $ie.Quit() 305 | 306 | } 307 | 308 | 309 | 310 | # Set mail object as read 311 | 312 | $mail.Unread = $false 313 | 314 | } 315 | 316 | Start-Sleep -s $Interval 317 | 318 | } 319 | 320 | } 321 | 322 | $InvokeMapShares = { 323 | 324 | Param( 325 | 326 | [Parameter(Position = 0, Mandatory=$True)] 327 | 328 | [int]$Interval 329 | 330 | ) 331 | 332 | 333 | 334 | While ($True) { 335 | 336 | $randShare = -join ((65..90) + (97..122) | Get-Random -Count 10 | % {[char]$_}) 337 | 338 | New-PSDrive -Name "K" -PSProvider FileSystem -Root "\\$randShare\sendCreds" 339 | 340 | Start-Sleep -s $Interval 341 | 342 | } 343 | 344 | } 345 | 346 | 347 | 348 | # Simulates a user browsing the Internet. Will open pseudo-random URLs 349 | 350 | $InvokeIETraffic = { 351 | 352 | Param( 353 | 354 | [Parameter(Position = 0, Mandatory=$True)] 355 | 356 | [int]$Interval, 357 | 358 | 359 | 360 | [Parameter(Position = 1, Mandatory=$True)] 361 | 362 | [int]$MaxLinkDepth 363 | 364 | ) 365 | 366 | 367 | 368 | $URIs = "https://news.google.com","https://www.reddit.com","https://www.msn.com","http://www.cnn.com","http://www.bbc.com","http://www.uroulette.com" 369 | 370 | $depth = 0 371 | 372 | 373 | 374 | $ie = New-Object -ComObject "InternetExplorer.Application" 375 | 376 | $ie.visible=$true 377 | 378 | While ($ie.Application -ne $null) { 379 | 380 | If ($depth -eq 0) { 381 | 382 | $requestUri = get-random -input $URIs 383 | 384 | } 385 | 386 | 387 | 388 | $depth = $depth + 1 389 | 390 | 391 | 392 | $ie.navigate2($requestUri) 393 | 394 | $timeout = 0 395 | 396 | While ($ie.busy -And $timeout -lt 10) { 397 | 398 | Start-Sleep -milliseconds 1000 399 | 400 | $timeout += 1 401 | 402 | } 403 | 404 | 405 | 406 | $linklist = New-Object System.Collections.ArrayList($null) 407 | 408 | ForEach ($link in $ie.document.getElementsByTagName("a")) { 409 | 410 | If ($link.href.length -gt 1) { 411 | 412 | $linklist.add($link.href) 413 | 414 | echo $link.href 415 | 416 | } 417 | 418 | } 419 | 420 | 421 | 422 | $requestUri = get-random -input $linklist 423 | 424 | echo $requestUri 425 | 426 | echo $depth 427 | 428 | Start-Sleep -s $Interval 429 | 430 | If ($depth -eq $MaxLinkDepth) { 431 | 432 | $depth = 0 433 | 434 | } 435 | 436 | } 437 | 438 | } 439 | 440 | 441 | 442 | $emailJob = 0 443 | 444 | $ieJob = 0 445 | 446 | $shareJob = 0 447 | 448 | 449 | 450 | If ($Email -or $All) { $emailJob = Start-Job -ScriptBlock $InvokeOpenEmail -ArgumentList @($EmailInterval) -Name 'usersimemail' } 451 | 452 | If ($IE -or $All) { $ieJob = Start-Job -ScriptBlock $InvokeIETraffic -ArgumentList @($PageDuration, $LinkDepth) -Name 'usersimie' } 453 | 454 | If ($Shares -or $All) { $shareJob = Start-Job -ScriptBlock $InvokeMapShares -ArgumentList @($MountInterval) -Name 'usersimshares' } 455 | 456 | 457 | 458 | # Start health check loop 459 | 460 | 461 | 462 | $StartTime = Get-Date 463 | 464 | $TimeOut = New-TimeSpan -Hours 1 465 | 466 | While ($True) { 467 | 468 | Start-Sleep -Seconds 60 469 | 470 | 471 | 472 | If (($All -or $Email) -and $emailJob.State -ne 'Running') { 473 | 474 | $emailJob = Start-Job -ScriptBlock $InvokeOpenEmail -ArgumentList @($EmailInterval) -Name 'usersimemail' 475 | 476 | } 477 | 478 | If (($All -or $IE) -and $ieJob.State -ne 'Running') { 479 | 480 | $ieJob = Start-Job -ScriptBlock $InvokeIETraffic -ArgumentList @($PageDuration, $LinkDepth) -Name 'usersimie' 481 | 482 | } 483 | 484 | If (($All -or $Shares) -and $shareJob.State -ne 'Running') { 485 | 486 | $shareJob = Start-Job -ScriptBlock $InvokeMapShares -ArgumentList @($MountInterval) -Name 'usersimshares' 487 | 488 | } 489 | 490 | 491 | 492 | If ((New-TimeSpan -Start $StartTime -End (Get-Date)) -gt $TimeOut) { 493 | 494 | If ($All -or $Email) { 495 | 496 | Stop-Job -Job $emailJob 497 | 498 | Stop-Process -Name outlook 499 | 500 | Stop-Process -Name werfault 501 | 502 | } 503 | 504 | If ($All -or $IE) { 505 | 506 | Stop-Job -Job $ieJob 507 | 508 | Stop-Process -Name iexplore 509 | 510 | Stop-Process -Name werfault 511 | 512 | } 513 | 514 | If ($All -or $Shares) { 515 | 516 | Stop-Job -Job $shareJob 517 | 518 | } 519 | 520 | $StartTime = Get-Date 521 | 522 | } 523 | 524 | } 525 | 526 | } 527 | 528 | 529 | 530 | If ($StandAlone) { 531 | 532 | # CLIENT BEHAVIOR 533 | 534 | If ($ConfigXML) { 535 | 536 | [xml]$XML = Get-Content $ConfigXML 537 | 538 | $EmailInterval = $XML.usersim.email.checkinInterval 539 | 540 | $PageDuration = $XML.usersim.web.pageDuration 541 | 542 | $LinkDepth = $XML.usersim.web.linkDepth 543 | 544 | $MountInterval = $XML.usersim.shares.mountInterval 545 | 546 | } Else { 547 | 548 | $EmailInterval = 300 549 | 550 | $PageDuration = 20 551 | 552 | $LinkDepth = 10 553 | 554 | $MountInterval = 30 555 | 556 | } 557 | 558 | 559 | 560 | # Make sure variables have values of the right type 561 | 562 | $myEmailInterval = if ($EmailInterval) {$EmailInterval} else {300} 563 | 564 | $myPageDuration = if ($PageDuration) {$PageDuration} else {20} 565 | 566 | $myLinkDepth = if ($LinkDepth) {$LinkDepth} else {10} 567 | 568 | $myMountInterval = if ($MountInterval) {$MountInterval} else {30} 569 | 570 | $myEmail = if ($Email) {1} else {0} 571 | 572 | $myIE = if ($IE) {1} else {0} 573 | 574 | $myShares = if ($Shares) {1} else {0} 575 | 576 | $myAll = if ($All) {1} else {0} 577 | 578 | 579 | 580 | Invoke-Command -ScriptBlock $RemoteScriptBlock -ArgumentList @($myEmailInterval, $myPageDuration, $myLinkDepth, $myMountInterval, $myEmail, $myIE, $myShares, $myAll) 581 | 582 | } Else { 583 | 584 | # SERVER BEHAVIOR 585 | 586 | If (!$ConfigXML) { 587 | 588 | Write-Host "Please provide a configuration file with '-ConfigXML' flag." 589 | 590 | Break 591 | 592 | } 593 | 594 | 595 | 596 | $ShareName = 'UserSim' 597 | 598 | 599 | 600 | Create-UserSimShare "C:\UserSim" $ShareName $RemoteScriptBlock 601 | 602 | 603 | 604 | [xml]$XML = Get-Content $ConfigXML 605 | 606 | $EmailInterval = $XML.usersim.email.checkinInterval 607 | 608 | $PageDuration = $XML.usersim.web.pageDuration 609 | 610 | $LinkDepth = $XML.usersim.web.linkDepth 611 | 612 | $MountInterval = $XML.usersim.shares.mountInterval 613 | 614 | $Server = $XML.usersim.serverIP 615 | 616 | 617 | 618 | $myEmailInterval = if ($EmailInterval) {$EmailInterval} else {300} 619 | 620 | $myPageDuration = if ($PageDuration) {$PageDuration} else {20} 621 | 622 | $myLinkDepth = if ($LinkDepth) {$LinkDepth} else {10} 623 | 624 | $myMountInterval = if ($MountInterval) {$MountInterval} else {30} 625 | 626 | $myEmail = if ($Email) {1} else {0} 627 | 628 | $myIE = if ($IE) {1} else {0} 629 | 630 | $myShares = if ($Shares) {1} else {0} 631 | 632 | $myAll = if ($All) {1} else {0} 633 | 634 | 635 | 636 | $TaskArgs = "-exec bypass -w hidden -c ipmo \\$Server\$ShareName\UserSim.ps1;Invoke-UserSim $myEmailInterval $myPageDuration $myLinkDepth $myMountInterval $myEmail $myIE $myShares $myAll" 637 | 638 | 639 | 640 | $XML.usersim.client | ForEach-Object { 641 | 642 | 643 | 644 | $myHost = $_.host 645 | 646 | $username = $_.username 647 | 648 | $password = $_.password 649 | 650 | $domain = $_.domain 651 | 652 | $DomainUser = $domain+'\'+$username 653 | 654 | 655 | 656 | Write-Host "In foreach loop..." 657 | 658 | Write-Host "$myHost" 659 | 660 | cmdkey /delete:"$myHost" 661 | 662 | Start-RemoteUserSimTask $myHost $TaskArgs $DomainUser $password 663 | 664 | start-sleep -s 1 665 | 666 | cmdkey /add:$myHost /user:"$domain\$username" /pass:"$password" 667 | 668 | Start-Sleep -s 1 669 | 670 | mstsc.exe /v "$myHost" 671 | 672 | Start-Sleep -s 5 673 | 674 | cmdkey /delete:"$myHost" 675 | 676 | 677 | 678 | Write-Host "Starting usersim on '$myHost' with username '$username'" 679 | 680 | } 681 | 682 | } 683 | 684 | } 685 | 686 | 687 | 688 | function Create-UserSimShare { 689 | 690 | [CmdletBinding()] 691 | 692 | Param( 693 | 694 | [Parameter(Position = 0, Mandatory=$True)] 695 | 696 | [String]$SharePath, 697 | 698 | 699 | 700 | [Parameter(Position = 1, Mandatory=$True)] 701 | 702 | [String]$ShareName, 703 | 704 | 705 | 706 | [Parameter(Position = 2, Mandatory=$True)] 707 | 708 | [Management.Automation.ScriptBlock]$ScriptBlock 709 | 710 | ) 711 | 712 | 713 | 714 | If (Test-Path $SharePath) { 715 | 716 | Remove-Item -Path $SharePath -Recurse -Force 717 | 718 | Start-Sleep -Milliseconds 400 719 | 720 | } 721 | 722 | New-Item $SharePath -Type Directory 723 | 724 | 725 | 726 | $Shares=[WMICLASS]"Win32_Share" 727 | 728 | 729 | 730 | $old_shares = Get-WMIObject Win32_Share -Filter "name='$ShareName'" 731 | 732 | If ($old_shares) { 733 | 734 | ForEach ($share in $old_shares) { 735 | 736 | $delete = $share.Delete() 737 | 738 | } 739 | 740 | } 741 | 742 | 743 | 744 | $Shares.Create($SharePath,$ShareName,0) 745 | 746 | $functionString = "function Invoke-UserSim {" + $ScriptBlock + "}" 747 | 748 | $functionString | Out-File $SharePath\UserSim.ps1 749 | 750 | } 751 | 752 | 753 | 754 | function Start-RemoteUserSimTask { 755 | 756 | [CmdletBinding()] 757 | 758 | Param( 759 | 760 | [Parameter(Position = 0, Mandatory=$True)] 761 | 762 | [String]$RemoteHost, 763 | 764 | 765 | 766 | [Parameter(Position = 1, Mandatory=$True)] 767 | 768 | [String]$TaskArguments, 769 | 770 | 771 | 772 | [Parameter(Position = 2, Mandatory=$True)] 773 | 774 | [String]$TaskUser, 775 | 776 | 777 | 778 | [Parameter(Position = 3, Mandatory=$True)] 779 | 780 | [String]$TaskPassword 781 | 782 | ) 783 | 784 | 785 | 786 | $RemoteStart = { 787 | 788 | Param( 789 | 790 | [Parameter(Position = 0, Mandatory=$True)] 791 | 792 | [String]$TaskArguments, 793 | 794 | 795 | 796 | [Parameter(Position = 1, Mandatory=$True)] 797 | 798 | [String]$TaskUser, 799 | 800 | 801 | 802 | [Parameter(Position = 2, Mandatory=$True)] 803 | 804 | [String]$TaskPassword 805 | 806 | ) 807 | 808 | 809 | 810 | $ExpTime = (Get-Date).AddMinutes(10).GetDateTimeFormats('s')[0] 811 | 812 | 813 | 814 | $ShedService = New-Object -comobject 'Schedule.Service' 815 | 816 | $ShedService.Connect('localhost') 817 | 818 | 819 | 820 | $Task = $ShedService.NewTask(0) 821 | 822 | $Task.RegistrationInfo.Description = 'Temporary User Sim Task' 823 | 824 | $Task.Settings.Enabled = $true 825 | 826 | $Task.Settings.AllowDemandStart = $true 827 | 828 | $Task.Settings.DeleteExpiredTaskAfter = 'PT5M' 829 | 830 | 831 | 832 | $trigger = $Task.Triggers.Create(11) 833 | 834 | $trigger.Enabled = $true 835 | 836 | $trigger.UserId = $TaskUser 837 | 838 | $trigger.StateChange = 3 839 | 840 | $trigger.EndBoundary = $ExpTime 841 | 842 | 843 | 844 | $trigger2 = $Task.Triggers.Create(9) 845 | 846 | $trigger2.Enabled = $true 847 | 848 | $trigger2.UserId = $TaskUser 849 | 850 | $trigger2.EndBoundary = $ExpTime 851 | 852 | 853 | 854 | $action = $Task.Actions.Create(0) 855 | 856 | $action.Path = "powershell" 857 | 858 | $action.Arguments = $TaskArguments 859 | 860 | 861 | 862 | $taskFolder = $ShedService.GetFolder("\") 863 | 864 | $taskFolder.RegisterTaskDefinition("UserSim", $Task , 6, $TaskUser, $TaskPassword, 3) 865 | 866 | } 867 | 868 | 869 | 870 | Invoke-Command -ScriptBlock $RemoteStart -ArgumentList @($TaskArguments, $TaskUser, $TaskPassword) -ComputerName $RemoteHost 871 | 872 | } 873 | 874 | 875 | 876 | function Invoke-ConfigureHosts { 877 | 878 | <# 879 | 880 | .SYNOPSIS 881 | 882 | 883 | 884 | Configure remote hosts in preperation for Invoke-UserSimulator 885 | 886 | 887 | 888 | .DESCRIPTION 889 | 890 | 891 | 892 | Sets some registry keys to allow programatic access to Outlook and prevent the "welcome" window 893 | 894 | in Internet Explorer. Also adds the user to run as to the "Remote Desktop Users" group on the 895 | 896 | remote computer. 897 | 898 | 899 | 900 | .PARAMETER ConfigXML 901 | 902 | 903 | 904 | The configuration xml file to use for host configuration on remote hosts. 905 | 906 | 907 | 908 | .EXAMPLE 909 | 910 | 911 | 912 | Import the script modules: 913 | 914 | PS>Import-Module .\Invoke-UserSimulator.ps1 915 | 916 | 917 | 918 | Configure remote hosts prior to running the script remotely: 919 | 920 | PS>Invoke-ConfigureHosts -ConfigXML .\config.xml 921 | 922 | 923 | 924 | #> 925 | 926 | Param( 927 | 928 | [Parameter(Position = 0, Mandatory=$True)] 929 | 930 | [String]$ConfigXML 931 | 932 | ) 933 | 934 | 935 | 936 | If (!$ConfigXML) { 937 | 938 | Write-Host "Please provide a configuration file with '-ConfigXML' flag." 939 | 940 | Break 941 | 942 | } else { 943 | 944 | [xml]$XML = Get-Content $ConfigXML 945 | 946 | 947 | 948 | $XML.usersim.client | ForEach-Object { 949 | 950 | 951 | 952 | $myHost = $_.host 953 | 954 | $username = $_.username 955 | 956 | $domain = $_.domain 957 | 958 | $domUser = "$domain\$username" 959 | 960 | 961 | 962 | $configBlock = { 963 | 964 | Param( 965 | 966 | [Parameter(Position = 0, Mandatory=$True)] 967 | 968 | [String]$domuser 969 | 970 | ) 971 | 972 | net localgroup "Remote Desktop Users" $domuser /add 973 | 974 | 975 | 976 | $registryPath = "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\REGISTRY\MACHINE\Software\Wow6432Node\Microsoft\Office\16.0\Outlook\Security" 977 | 978 | New-Item -Path $registryPath -Force 979 | 980 | Set-ItemProperty -Path $registryPath -Name ObjectModelGuard -Value 2 -Type DWord 981 | 982 | 983 | 984 | $registryPath = "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\REGISTRY\MACHINE\Software\Microsoft\Office\16.0\Outlook\Security" 985 | 986 | New-Item -Path $registryPath -Force 987 | 988 | Set-ItemProperty -Path $registryPath -Name ObjectModelGuard -Value 2 -Type DWord 989 | 990 | 991 | 992 | $registryPath = "HKLM:\Software\Policies\Microsoft\Internet Explorer\Main" 993 | 994 | New-Item -Path $registryPath -Force 995 | 996 | Set-ItemProperty -Path $registryPath -Name DisableFirstRunCustomize -Value 1 997 | 998 | } 999 | 1000 | 1001 | 1002 | Invoke-Command -ScriptBlock $configBlock -ArgumentList $domuser -ComputerName $myHost 1003 | 1004 | } 1005 | 1006 | } 1007 | 1008 | } 1009 | Start outlook.exe 1010 | Invoke-UserSimulator -StandAlone -Email -------------------------------------------------------------------------------- /Modules/Tools.ps1: -------------------------------------------------------------------------------- 1 | 2 | 3 | <# 4 | .Synopsis 5 | Function to email a specified log file. 6 | .DESCRIPTION 7 | This funtion is designed to email a log file to a user or distribution list. 8 | 9 | .NOTES 10 | Created by: Jason Wasser @wasserja 11 | Modified: 4/2/2015 11:42:45 AM 12 | Version 1.3 13 | Changelog: 14 | * Added authentication support with default of anonymous. Send-MailMessage 15 | with Exchange forces authentication. 16 | * Changed to use Send-MailMessage 17 | 18 | To Do: 19 | * Add ability to prompt for credentials 20 | * secure password 21 | .EXAMPLE 22 | Send-Log -Path "C:\Logs\Reboot.log" 23 | Sends the C:\Logs\Reboot.log to the recipient in the script parameters. 24 | .EXAMPLE 25 | Send-Log -Path c:\Logs\install.log -to admin@domain.com -from no-reply@domain.com -subject "See attached Log" -messagebody "See attached" -smtpserver smtp.domain.com 26 | #> 27 | function sendlogmail 28 | { 29 | [CmdletBinding()] 30 | [OutputType([int])] 31 | Param 32 | ( 33 | # Enter the path for the log file to be emailed. 34 | [Parameter(Mandatory=$true, 35 | ValueFromPipelineByPropertyName=$true, 36 | Position=0)] 37 | [Alias("Attachment","LogPath")] 38 | $Path, 39 | [string]$SmtpServer = "smtp.domain.com", 40 | [string]$ToAddress = "email@domain.com", 41 | [string]$FromAddress = "usersimulationbot@domain.com", 42 | [string]$Subject = "Automaton Alert $(get-date -Format "MM/dd/yyyy HH:mm")", 43 | [string]$MessageBody = "Please see attached.`n`nSincerely,`nYour friendly AutoMaton.", 44 | [string]$Username = "anonymous", 45 | [string]$Password = "anonymous", 46 | [int]$Port=25 47 | ) 48 | 49 | Begin 50 | { 51 | } 52 | Process 53 | { 54 | if (Test-Path $Path) { 55 | 56 | # SMTP Authentication 57 | $SecurePassword = ConvertTo-SecureString -String $Password -AsPlainText -Force 58 | $Credential = New-Object System.Management.Automation.PSCredential($Username,$SecurePassword) 59 | 60 | #Sending email 61 | Write-Verbose "Sending $Path via SMTP." 62 | Send-MailMessage -To $ToAddress -From $FromAddress -Subject $Subject -Body $MessageBody -Attachments $Path -SmtpServer $smtpServer -Credential $Credential -Port $Port 63 | } 64 | else { 65 | Write-Error "Unable to find $Path." 66 | } 67 | } 68 | End 69 | { 70 | } 71 | } 72 | 73 | 74 | 75 | function Start-Process-Active 76 | { 77 | param 78 | ( 79 | [System.Management.Automation.Runspaces.PSSession]$Session, 80 | [string]$Executable, 81 | [string]$Argument, 82 | [string]$WorkingDirectory, 83 | [string]$UserID, 84 | [switch]$Verbose = $false 85 | 86 | ) 87 | 88 | if (($Session -eq $null) -or ($Session.Availability -ne [System.Management.Automation.Runspaces.RunspaceAvailability]::Available)) 89 | { 90 | $Session.Availability 91 | throw [System.Exception] "Session is not availabile" 92 | } 93 | 94 | Invoke-Command -Session $Session -ArgumentList $Executable,$Argument,$WorkingDirectory,$UserID -ScriptBlock { 95 | param($Executable, $Argument, $WorkingDirectory, $UserID) 96 | $action = New-ScheduledTaskAction -Execute $Executable -Argument $Argument -WorkingDirectory $WorkingDirectory 97 | $principal = New-ScheduledTaskPrincipal -userid $UserID 98 | $task = New-ScheduledTask -Action $action -Principal $principal 99 | $taskname = "_StartProcessActiveTask" 100 | try 101 | { 102 | $registeredTask = Get-ScheduledTask $taskname -ErrorAction SilentlyContinue 103 | } 104 | catch 105 | { 106 | $registeredTask = $null 107 | } 108 | if ($registeredTask) 109 | { 110 | Unregister-ScheduledTask -InputObject $registeredTask -Confirm:$false 111 | } 112 | $registeredTask = Register-ScheduledTask $taskname -InputObject $task 113 | 114 | Start-ScheduledTask -InputObject $registeredTask 115 | 116 | Unregister-ScheduledTask -InputObject $registeredTask -Confirm:$false 117 | } 118 | 119 | } 120 | 121 | 122 | 123 | 124 | function log_header 125 | { 126 | Param([int]$round_counter, 127 | [Parameter(Mandatory=$False)] 128 | [bool]$display=$true) 129 | 130 | if ($display){ 131 | "" 132 | "" 133 | "==============================================================" 134 | "=========== Round N:" + [string]$round_counter+ " at time:" + (Get-Date) + " ============" 135 | "==============================================================" 136 | } 137 | } 138 | 139 | 140 | 141 | 142 | function Use-RunAs 143 | { 144 | # Check if script is running as Adminstrator and if not use RunAs 145 | # Use Check Switch to check if admin 146 | 147 | param([Switch]$Check) 148 | 149 | $IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()` 150 | ).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") 151 | 152 | if ($Check) { return $IsAdmin } 153 | 154 | if ($MyInvocation.ScriptName -ne "") 155 | { 156 | if (-not $IsAdmin) 157 | { 158 | try 159 | { 160 | $arg = "-file `"$($MyInvocation.ScriptName)`"" 161 | Start-Process "$psHome\powershell.exe" -Verb Runas -ArgumentList $arg -ErrorAction 'stop' 162 | } 163 | catch 164 | { 165 | Write-Warning "Error - Failed to restart script with runas" 166 | "This script needs Admin rights to run! Exiting now" 167 | Start-Sleep -s 6 168 | break 169 | } 170 | exit # Quit this session of powershell 171 | } 172 | } 173 | else 174 | { 175 | Write-Warning "Error - Script must be saved as a .ps1 file first" 176 | break 177 | } 178 | } 179 | 180 | 181 | 182 | 183 | function something-to-do 184 | { 185 | Param([hashtable]$hash) 186 | $empty = $true 187 | 188 | $hash.keys | % {$empty = ($empty -or $hash.Item($_))} 189 | 190 | return $empty 191 | 192 | } 193 | 194 | 195 | function read-txt-line 196 | { 197 | Param([int]$line) 198 | $linecontent = @((Get-Content .\schedule.txt -TotalCount $line))[-1] 199 | 200 | $Action, $Parameter, $activity, $on, $PC, $timetype, $time = ($linecontent.ToLower()).Split(' ') 201 | $hr, $min = $time.Split(':') 202 | $minutes = [int]$hr * 60 + [int]$min 203 | #TODO trsanfert this to actions 204 | 205 | 206 | return $Action, $Parameter, $PC, $timetype, $minutes 207 | } 208 | 209 | 210 | 211 | function change-variables 212 | { 213 | [CmdletBinding()] 214 | Param( 215 | [Parameter(Mandatory=$False)] 216 | [hashtable]$IEhash, 217 | [Parameter(Mandatory=$False)] 218 | [hashtable]$Maphash, 219 | [Parameter(Mandatory=$False)] 220 | [hashtable]$Typehash, 221 | [Parameter(Mandatory=$False)] 222 | [string]$Action, 223 | [Parameter(Mandatory=$False)] 224 | [string]$Parameter, 225 | [Parameter(Mandatory=$False)] 226 | [string]$PC, 227 | [Parameter(Mandatory=$False)] 228 | [int]$line_number, 229 | [Parameter(Mandatory=$False)] 230 | [array]$PClist 231 | ) 232 | $bool = [bool]($Action -eq "start") 233 | 234 | if ($PC -eq "all"){ 235 | if ($Parameter -eq "ie" -or $Parameter -eq "all"){$PClist | ForEach-Object {$IEhash.set_item($_, $bool)}} 236 | if ($Parameter -eq "mapshare" -or $Parameter -eq "all"){$PClist| ForEach-Object {$Maphash.set_item($_, $bool)}} 237 | if ($Parameter -eq "typing" -or $Parameter -eq "all"){$PClist| ForEach-Object {$Typehash.set_item($_, $bool)}} 238 | } 239 | 240 | 241 | elseif ($PC -eq "first" ){ 242 | $PC = $PClist[0] 243 | if ($Parameter -eq "ie" -or $Parameter -eq "all"){$IEhash.set_item($PC, $bool)} 244 | if ($Parameter -eq "mapshare" -or $Parameter -eq "all"){$Maphash.set_item($PC, $bool)} 245 | if ($Parameter -eq "typing" -or $Parameter -eq "all"){$Typehash.set_item($PC, $bool)} 246 | 247 | } 248 | elseif ($PC -eq "random" ){ 249 | $nb_pc = Get-Random -Maximum $PClist.Length 250 | if ($Parameter -eq "ie" -or $Parameter -eq "all"){$PClist| Get-Random -Count $nb_pc | ForEach-Object {$IEhash.set_item($_, $bool)}} 251 | if ($Parameter -eq "mapshare" -or $Parameter -eq "all"){$PClist| Get-Random -Count $nb_pc | ForEach-Object {$Maphash.set_item($_, $bool)}} 252 | if ($Parameter -eq "typing" -or $Parameter -eq "all"){$PClist| Get-Random -Count $nb_pc | ForEach-Object {$Typehash.set_item($_, $bool)}} 253 | 254 | } 255 | else { #so it must be a particular PC name 256 | if ($Parameter -eq "ie" -or $Parameter -eq "all"){$IEhash.set_item($PC, $bool)} 257 | if ($Parameter -eq "mapshare" -or $Parameter -eq "all"){$Maphash.set_item($PC, $bool)} 258 | if ($Parameter -eq "typing" -or $Parameter -eq "all"){$Typehash.set_item($PC, $bool)} 259 | } 260 | 261 | 262 | $lastactiontimestamp = [int]((Get-Date -UFormat "%R").Split(":")[0]) * 60 + [int]((Get-Date -UFormat "%R").Split(":")[1]) 263 | return $IEhash, $Maphash, $Typehash, ($line_number+1), $lastactiontimestamp 264 | 265 | } 266 | 267 | 268 | 269 | 270 | 271 | function update-schedule 272 | <#return a new set of variables for each scheduled event 273 | #> 274 | { 275 | [CmdletBinding()] 276 | Param( 277 | [Parameter(Mandatory=$False)] 278 | [hashtable]$IEhash, 279 | [Parameter(Mandatory=$False)] 280 | [hashtable]$Maphash, 281 | [Parameter(Mandatory=$False)] 282 | [hashtable]$Typehash, 283 | [Parameter(Mandatory=$False)] 284 | [int]$line_number, 285 | [Parameter(Mandatory=$False)] 286 | [int]$lastactiontimestamp, 287 | [Parameter(Mandatory=$False)] 288 | [array]$PClist 289 | ) 290 | 291 | $CurrentAction, $CurrentParameter, $CurrentPC, $Currenttimetype, $Currentminutes = read-txt-line -line $line_number 292 | $NextAction, $NextParameter, $NextPC, $Nexttimetype, $Nextminutes = read-txt-line -line ($line_number+1) 293 | $now = [int]((Get-Date -UFormat "%R").Split(":")[0]) * 60 + [int]((Get-Date -UFormat "%R").Split(":")[1]) #return time of the day in minutes 294 | 295 | 296 | if ($NextAction, $NextParameter, $NextPC, $Nexttimetype, $Nextminutes -eq $CurrentAction, $CurrentParameter, $CurrentPC, $Currenttimetype, $Currentminutes){ 297 | return $IEhash,$Maphash, $Typehash, ($line_number+1), $lastactiontimestamp 298 | }#do not change anything if two same lines or last line of the file 299 | 300 | 301 | 302 | 303 | if ($Nexttimetype -eq "at"){ 304 | if ($now -ge $Nextminutes){ 305 | return (change-variables -IEhash $IEhash -Maphash $Maphash -Typehash $Typehash -Action $NextAction -Parameter $NextParameter -PC $NextPC -line_number $line_number -PClist $PClist) 306 | } 307 | } 308 | 309 | 310 | 311 | if ($Nexttimetype -eq "after"){ 312 | $diff = $now - $lastactiontimestamp 313 | if ($diff -ge $Nextminutes){ 314 | return (change-variables -IEhash $IEhash -Maphash $Maphash -Typehash $Typehash -Action $NextAction -Parameter $NextParameter -PC $NextPC -line_number $line_number -PClist $PClist) 315 | } 316 | } 317 | 318 | return $IEhash, $Maphash,$Typehash, $line_number, $lastactiontimestamp 319 | 320 | } 321 | 322 | 323 | -------------------------------------------------------------------------------- /Modules/Type.ps1: -------------------------------------------------------------------------------- 1 | #type module 2 | 3 | Set-Location $args[0] 4 | 5 | 6 | 7 | Try{. .\Config.ps1 -ErrorAction Stop} 8 | Catch { 9 | Write-Host Config File could not ne loaded. Looks like you removed the config file, left it open, or that you made a error when ediditng it. 10 | Write-Host This program will exit in one minute 11 | Start-Sleep -s 60 12 | Break 13 | } 14 | 15 | 16 | 17 | 18 | 19 | 20 | function simulate-keys{ 21 | 22 | [CmdletBinding()] 23 | Param( 24 | [Parameter(Mandatory=$False)] 25 | [bool]$save=$true 26 | ) 27 | 28 | 29 | 30 | Try{ 31 | 32 | $text = Get-Content Misc\to_write.txt 33 | $text = $text.Split(" ") 34 | 35 | 36 | 37 | $wshell = new-object -comObject wscript.shell -ErrorAction Stop 38 | 39 | "This will not go in the file, but just in case there is a problem so there are still keystrokes password is johnsmith1985" | ForEach {$word = $_;$word = $word.Split("");$word | foreach {Start-Sleep -Milliseconds $Type_speed; $wshell.SendKeys($_)};$wshell.sendkeys(" ")} 40 | 41 | start notepad Misc\written.txt 42 | $null = $wshell.AppActivate('written - Notepad') 43 | Sleep 1 44 | 45 | 46 | #clear file 47 | $wshell.SendKeys("^{a}") 48 | $wshell.SendKeys("{DEL}") 49 | 50 | 51 | 52 | #sending keystrokes, with letters well separated, one after each other 53 | 54 | $text | ForEach { 55 | $word = $_ 56 | $word = $word.Split("") 57 | 58 | $word | foreach { 59 | Start-Sleep -Milliseconds $Type_speed 60 | $wshell.SendKeys($_) 61 | } 62 | 63 | $wshell.sendkeys(" ") 64 | } 65 | 66 | 67 | 68 | if ($save){$wshell.SendKeys("^{s}");$wshell.SendKeys("%{F4}")} 69 | 70 | } 71 | Catch{ 72 | "Failed to send keystrokes to text editor" 73 | } 74 | if (!$Error){"Result: Typing simulation completed succesfully"} 75 | } 76 | 77 | 78 | 79 | 80 | simulate-keys 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-user-simulation 2 | 3 | powershell script to simulate activity by a user 4 | 5 | ## Why does it exist? 6 | 7 | Improve the experience of cybersecurity exercises. The script can be run on multiples VM/PCs to simulate the activity of real users, and generate traffic on the network. 8 | 9 | ## This set of powershell scripts simulate on W10/W7 the activity of an user 10 | 11 | It is strongly recommended you read the documentation in Docs before attempting anything. 12 | 13 | ## Features 14 | 15 | - Run automatically for a specified time 16 | - No-fail policy, the parts of the script can fail (for eg if network is too slow) but whatever can still run will keep running 17 | - Log everthing, logs can be emailed (experimental) 18 | - Run locally or on multiple (networked) machines 19 | - Simulate: Internet Explorer browsing, Typing, accessing a shared drive, open outlook (desktop, tested on W7) and open any (executable) attachment, and any link. 20 | -------------------------------------------------------------------------------- /RunUserSimulation.ps1: -------------------------------------------------------------------------------- 1 | 2 | 3 | #just in case it was launched from the wrong folder, as the present working directory 4 | # NEEDS to be the one containing the script (because of relative-path dot sourcing other modules) 5 | $script_path = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent 6 | cd $script_path 7 | 8 | 9 | 10 | #generate logs 11 | $ErrorActionPreference="SilentlyContinue" 12 | Stop-Transcript | out-null 13 | $ErrorActionPreference = "Continue" 14 | $LogName = Get-Date -Format "yyyy-MM-dd-----HH-mm-ss" 15 | $global:global_Log = ".\Logs\" + $LogName + ".txt" 16 | Start-Transcript -path $global_Log -append -IncludeInvocationHeader 17 | 18 | 19 | 20 | 21 | # Start the actual script 22 | 23 | 24 | 25 | #import config and tools, and modules 26 | Try{. .\Config.ps1 -ErrorAction Stop} 27 | Catch { 28 | Write-Host Config File could not ne loaded. Looks like you removed the config file, left it open, or that you made a error when ediditng it. 29 | Write-Host This program will exit in one minute 30 | Start-Sleep -s 60 31 | Break 32 | } 33 | 34 | Try{. .\Modules\Tools.ps1 -ErrorAction Stop} 35 | Catch { 36 | Write-Host One or more module could not be loaded. 37 | Write-Host This program will exit in one minute 38 | Start-Sleep -s 60 39 | Break 40 | } 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | function simulate-user 52 | { 53 | <# 54 | .SYNOPSIS 55 | This function simulate user behavior on the local windows systems. Author: Michael Bleuez. All rights belong to RHEA GROUP 56 | 57 | 58 | .PARAMETER Unactivity 59 | Performs selected actions each "Unactivity" seconds (default 2s); a higher unactivity will generate less traffic & user activity. Must be a positive integer 60 | 61 | .PARAMETER actiontimeout 62 | Set timeout in seconds for each action to be perfomed so the script does not hang (default 60s). 63 | 64 | .PARAMETER duration 65 | How long the whole programm should run (in minutes). 0 means no limit 66 | 67 | .PARAMETER All 68 | Equivalent to all parameters below 69 | 70 | .PARAMETER IE 71 | Turn on Internet Explorer (random) navigation 72 | 73 | .PARAMETER MapShare 74 | Turn on network shares mapping 75 | 76 | .NOTES 77 | Some of the powershell cmdlets require powershell version 3+ 78 | 79 | 80 | #> 81 | [CmdletBinding()] 82 | Param( 83 | [Parameter(Mandatory=$False)] 84 | [bool]$IE, 85 | [Parameter(Mandatory=$False)] 86 | [bool]$MapShare, 87 | [Parameter(Mandatory=$False)] 88 | [bool]$Typing, 89 | [Parameter(Mandatory=$False)] 90 | [bool]$schedule, 91 | [Parameter(Mandatory=$False)] 92 | [int]$Unactivity = 2, 93 | [Parameter(Mandatory=$False)] 94 | [int]$actiontimeout = 60, 95 | [Parameter(Mandatory=$False)] 96 | [int]$duration = 0 97 | ) 98 | 99 | 100 | $pc = "all" 101 | $list_computer = ,$pc 102 | $IEhash = @{} 103 | $Maphash = @{} 104 | $Typehash = @{} 105 | 106 | $IEhash.Add($pc, $IE) 107 | $Maphash.Add($pc, $MapShare) 108 | $Typehash.Add($pc, $Typing) 109 | 110 | 111 | "Start of operations" 112 | $round_counter = 0 113 | $startDate = Get-Date 114 | $lastactiontimestamp = [int]((Get-Date -UFormat "%R").Split(":")[0]) * 60 + [int]((Get-Date -UFormat "%R").Split(":")[1])#time of the day but in minutes 115 | $line_number = 1 116 | $displayheader = $true 117 | 118 | while (($duration -eq 0) -or ($startDate.AddMinutes($duration) -gt (Get-Date))) { 119 | Start-Sleep -s $Unactivity #sleep for unactivity time 120 | log_header $round_counter $displayheader 121 | $round_counter += $displayheader #only increase count if we displayed the previous header 122 | $displayheader = $false #set to false to only display non-empty round headers 123 | 124 | 125 | if ($schedule) {$IEhash, $Maphash, $Typehash, $line_number, $lastactiontimestamp = update-schedule -IEhash $IEhash -Maphash $Maphash -Typehash $Typehash -line_number $line_number -lastactiontimestamp $lastactiontimestamp -PClist $list_computer} 126 | 127 | 128 | ########################################################################place new features under this 129 | 130 | <#if (<$switchhash>[$pc]){ 131 | $job = Start-Job -Name -ArgumentList $pwd -FilePath Modules\name.ps1" 132 | Get-Job | Wait-Job -Timeout $actiontimeout | Receive-Job 133 | 134 | #failsafe so no stray windows/process 135 | $temp = (Get-Job -Name -ErrorAction SilentlyContinue| Stop-Job) 136 | "----------------------------------------------------------" 137 | $displayheader = $true 138 | Start-Sleep -s $Unactivity 139 | }#> #template 140 | 141 | if ($IEhash[$pc]){ 142 | $job = Start-Job -Name EIsimu -ArgumentList $pwd -FilePath .\Modules\IE.ps1 143 | Get-Job | Wait-Job -Timeout $actiontimeout | Receive-Job 144 | 145 | 146 | #failsafe so no stray windows/process 147 | $temp = (Get-Job -Name EIsimu -ErrorAction SilentlyContinue| Stop-Job) 148 | $temp = (Get-Process iexplore -ErrorAction SilentlyContinue | Stop-Process) 149 | $temp = (Get-Process ielowutil -ErrorAction SilentlyContinue | Stop-Process) 150 | "----------------------------------------------------------" 151 | $displayheader = $true 152 | Start-Sleep -s $Unactivity 153 | } #generate IE activity 154 | 155 | 156 | if ($Maphash[$pc]){ 157 | $job = Start-Job -Name MapShare -ArgumentList $pwd -FilePath .\Modules\MapShare.ps1 158 | Get-Job | Wait-Job -Timeout $actiontimeout | Receive-Job 159 | 160 | 161 | #failsafe so no stray windows/process 162 | (Get-Job MapSharesimu -ErrorAction SilentlyContinue | Stop-Job) |Out-Null 163 | "-------------------------------------------------------------------------------" 164 | $displayheader = $true 165 | Start-Sleep -s $Unactivity 166 | } #generate shares map activity 167 | 168 | 169 | if ($Typehash[$pc]){ 170 | $job = Start-Job -Name Typekey -ArgumentList $pwd -FilePath "Modules\Type.ps1" 171 | Get-Job | Wait-Job -Timeout $actiontimeout | Receive-Job 172 | 173 | 174 | #failsafe so no stray windows/process 175 | $temp = (Get-Job -Name Typekey -ErrorAction SilentlyContinue| Stop-Job) 176 | "----------------------------------------------------------" 177 | $displayheader = $true 178 | Start-Sleep -s $Unactivity 179 | }#generate key strokes activity 180 | 181 | 182 | 183 | #######################################################################place new features above this 184 | 185 | }#end of while 186 | 187 | 188 | #stop the transcript 189 | Stop-Transcript 190 | 191 | if ($global_email){ 192 | sendlogmail -Path $global_Log -SmtpServer $PSEmailServer -ToAddress $email_address 193 | } 194 | 195 | 196 | "This is the end of the user simulation" 197 | "Script will exit in one minute" 198 | Start-Sleep -s 60 199 | 200 | } 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | function simulate-user-server 219 | { 220 | <# 221 | .SYNOPSIS 222 | This function simulate user behavior on multiple (networked) windows systems. Author: Michael Bleuez. All rights belong to RHEA GROUP 223 | See simulate-user for the common parameters. 224 | 225 | .PARAMETER autodetect 226 | If the computer is to detect automatically the PCs on the same network. Not reliable 227 | 228 | .PARAMETER PClist 229 | In case autodetect is disabled, provide a list of the PCs of the network to send tasks to. 230 | 231 | 232 | 233 | 234 | .NOTES 235 | Some of the powershell cmdlets require powershell version 3+ 236 | 237 | 238 | #> 239 | [CmdletBinding()] 240 | Param( 241 | [Parameter(Mandatory=$False)] 242 | [bool]$IE, 243 | [Parameter(Mandatory=$False)] 244 | [bool]$MapShare, 245 | [Parameter(Mandatory=$False)] 246 | [bool]$Typing, 247 | [Parameter(Mandatory=$False)] 248 | [bool]$schedule, 249 | [Parameter(Mandatory=$False)] 250 | [int]$Unactivity = 2, 251 | [Parameter(Mandatory=$False)] 252 | [int]$actiontimeout = 60, 253 | [Parameter(Mandatory=$False)] 254 | [int]$duration = 0, 255 | [Parameter(Mandatory=$False)] 256 | [bool]$autodetect = $true, 257 | [Parameter(Mandatory=$False)] 258 | [array]$PClist 259 | ) 260 | 261 | 262 | Use-RunAs 263 | "Running as administrator" 264 | 265 | if ($autodetect){ 266 | $list_computer_dirty = net view 267 | $list_computer = @() 268 | ForEach ($line in $list_computer_dirty){ 269 | if ($line.StartsWith("\\")){ 270 | $list_computer += $line.substring(2) -replace '(^\s+|\s+$)','' -replace '\s+',' ' 271 | } 272 | } 273 | "Detected Computers:" 274 | $list_computer 275 | "Start of operations" 276 | } 277 | else { 278 | $list_computer = $PClist 279 | "Computers on the network:" 280 | $list_computer 281 | "Start of operations" 282 | } 283 | 284 | 285 | 286 | $IEhash = @{} 287 | $Maphash = @{} 288 | $Typehash = @{} 289 | 290 | foreach ($pc in $list_computer){ 291 | $IEhash.Add($pc, $IE) 292 | $Maphash.Add($pc, $MapShare) 293 | $Typehash.Add($pc, $Typing) 294 | } 295 | 296 | 297 | 298 | 299 | 300 | $round_counter = 0 301 | $startDate = Get-Date 302 | $lastactiontimestamp = [int]((Get-Date -UFormat "%R").Split(":")[0]) * 60 + [int]((Get-Date -UFormat "%R").Split(":")[1]) #time of the day but in minutes 303 | $line_number = 1 304 | $displayheader = $true 305 | 306 | while (($duration -eq 0) -or ($startDate.AddMinutes($duration) -gt (Get-Date))) { 307 | Start-Sleep -s $Unactivity 308 | log_header $round_counter $displayheader 309 | $round_counter += $displayheader #only increase count if we displayed the previous header 310 | 311 | if ($schedule) {$IEhash, $Maphash, $Typehash, $line_number, $lastactiontimestamp = update-schedule -IEhash $IEhash -Maphash $Maphash -Typehash $Typehash -line_number $line_number -lastactiontimestamp $lastactiontimestamp -PClist $list_computer} 312 | 313 | ########################################################################place new features under this 314 | 315 | 316 | if ($true){ 317 | "#####IE activity####" 318 | Try { 319 | foreach ($pc in $list_computer){ 320 | if ($IEhash[$pc]){ 321 | $job = Invoke-Command -ComputerName $pc -ArgumentList $pwd -FilePath .\Modules\IE.ps1 -AsJob -JobName $pc -ErrorAction Stop 322 | } 323 | } 324 | 325 | #wait for all to complete or timeout 326 | Get-Job | Wait-Job -Timeout $actiontimeout | Receive-Job 327 | 328 | #failsafe 329 | "Exiting activity" 330 | foreach ($pc in $list_computer){ 331 | if ($IEhash[$pc]){ 332 | (Get-Job IEsimu -ErrorAction SilentlyContinue | Stop-Job) |Out-Null -ErrorAction Stop 333 | $temp = Invoke-Command -ComputerName $pc -ScriptBlock {Get-Process iexplore -ErrorAction SilentlyContinue | Stop-Process} -ErrorAction Stop 334 | $temp = Invoke-Command -ComputerName $pc -ScriptBlock {Get-Process ielowutil -ErrorAction SilentlyContinue | Stop-Process} -ErrorAction Stop 335 | } 336 | } 337 | "----------------------------------------------------------" 338 | } 339 | Catch {"Failed to send command to remote PC"} 340 | } #generate IE activity 341 | 342 | Start-Sleep -s $Unactivity 343 | 344 | if ($true){ 345 | "#####Map Shares activity#####" 346 | Try { 347 | foreach ($pc in $list_computer){ 348 | if ($Maphash[$pc]){ 349 | $job = Invoke-Command -ComputerName $pc -ArgumentList $pwd -FilePath .\Modules\MapShare.ps1 -AsJob -JobName MapShares -ErrorAction Stop 350 | } 351 | } 352 | 353 | #wait for all to complete or timeout 354 | Get-Job | Wait-Job -Timeout $actiontimeout | Receive-Job 355 | 356 | #failsafe so no stray windows/process 357 | "Exiting activity" 358 | foreach ($pc in $list_computer){ 359 | if ($Maphash[$pc]){$temp = Invoke-Command -ComputerName $pc -ScriptBlock {(Get-Job MapShares -ErrorAction SilentlyContinue | Stop-Job) |Out-Null} -ErrorAction Stop} 360 | } 361 | "----------------------------------------------------------" 362 | } 363 | catch {"Failed to send command to remote PC"} 364 | } #generate shares map activity 365 | 366 | Start-Sleep -s $Unactivity 367 | 368 | if ($true){ 369 | "#####Typing activity#####" 370 | Try { 371 | foreach ($pc in $list_computer){ 372 | if ($Typehash[$pc]){ 373 | $job = Invoke-Command -ComputerName $pc -ArgumentList $pwd -FilePath .\Modules\Type.ps1 -AsJob -JobName Type -ErrorAction Stop 374 | } 375 | } 376 | 377 | #wait for all to complete or timeout 378 | Get-Job | Wait-Job -Timeout $actiontimeout | Receive-Job 379 | 380 | #failsafe so no stray windows/process 381 | "Exiting activity" 382 | foreach ($pc in $list_computer){ 383 | if ($Typehash[$pc]){$temp = Invoke-Command -ComputerName $pc -ScriptBlock {(Get-Job Type -ErrorAction SilentlyContinue | Stop-Job) |Out-Null} -ErrorAction Stop} 384 | } 385 | "----------------------------------------------------------" 386 | } 387 | catch {"Failed to send command to remote PC"} 388 | } #generate shares map activity 389 | 390 | 391 | #######################################################################place new features above this 392 | 393 | }#end of while 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | } 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | #Launch the correct script 422 | 423 | if (!$global_standalone){ 424 | "If you see an empty log, or few logs starting at about the same time, only the most recent will be significant";"The formers are 'orphan' logs stopped when the script was relaunched with admin rights (after the UAC prompt)" 425 | "Running in multisession mode" 426 | simulate-user-server -IE $global_IE -MapShare $global_MapShare -Typing $global_type -schedule $global_schedule -Unactivity $global_unactivity -actiontimeout $global_actiontimeout -duration $global_duration -autodetect $global_autodetect -PClist $global_listPCs 427 | } 428 | else { 429 | "Running on the local session only" 430 | simulate-user -IE $global_IE -MapShare $global_MapShare -Typing $global_type -schedule $global_schedule -Unactivity $global_unactivity -actiontimeout $global_actiontimeout -duration $global_duration 431 | } 432 | 433 | "This is the end of the user simulation" 434 | "Script will exit in one minute" 435 | Start-Sleep -s 60 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | #stop the transcrit if it was not stopped before 452 | $ErrorActionPreference="SilentlyContinue" 453 | Stop-Transcript | out-null 454 | $ErrorActionPreference = "Continue" -------------------------------------------------------------------------------- /schedule.txt: -------------------------------------------------------------------------------- 1 | Stop All activity on All after 00:01 2 | Start IE activity on All after 00:01 3 | Stop All activity on All after 00:01 4 | --------------------------------------------------------------------------------