PSSplunkSearch.psm1
Function Connect-Splunk { <# .SYNOPSIS Script to establish a connection to Splunk .DESCRIPTION Script to establish a connection to Splunk .PARAMETER Server Name of the Splunk server .PARAMETER Port Port number used by the Splunk server, default is 8089 .PARAMETER Credential Username and password needed to authenticate .EXAMPLE Connect-Splunk -Server splunk.yourdomain.com .EXAMPLE Connect-Splunk -Server splunk.yourdomain.com -Port 9999 .NOTES Author: Martin Pugh Twitter: @martin9700 Spiceworks: Martin9700 Blog: www.thesurlyadmin.com Changelog: 02/27/21 Initial Release .LINK https://github.com/martin9700/PSSplunkSearch #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true, Position=0)] [string]$Server, [Parameter(Mandatory=$false, Position=1)] [int]$Port = 8089, [Parameter(Mandatory=$true)] [pscredential]$Credential ) If ((-not (Get-Variable SplunkConnect -Scope Script -ErrorAction SilentlyContinue)) -or $Script:SplunkConnect.Expires -lt (Get-Date)) { $AuthSplat = @{ Uri = "https://$($server):$port/services/auth/login" UseBasicParsing = $true Body = "username=$($Credential.UserName);password=$($Credential.GetNetworkCredential().Password)" Method = "Post" ContentType = "application/x-www-form-urlencoded" ErrorAction = "Stop" } Try { $Return = Invoke-RestMethod @AuthSplat } Catch { Write-Error "Unable to authenticate to Splunk ($server), error: $_" } $Header = @{ Authorization = "Splunk $($Return.response.sessionKey)" } $Script:SplunkConnect = [PSCustomObject]@{ BaseUri = "https://$($server):$port" Header = $Header Expires = (Get-Date).AddHours(6) } } Else { Write-Verbose "Already have a valid connection to Splunk" } } Function Disconnect-Splunk { <# .SYNOPSIS Script to delete the connection variable to Splunk .DESCRIPTION Script to delete the connection variable to Splunk .EXAMPLE Disconnect-Splunk .NOTES Author: Martin Pugh Twitter: @martin9700 Spiceworks: Martin9700 Blog: www.thesurlyadmin.com Changelog: 02/27/21 Initial Release .LINK https://github.com/martin9700/PSSplunkSearch #> [CmdletBinding()] Param () Process { Remove-Variable -Name SplunkConnect -Scope Script -ErrorAction SilentlyContinue } } Function Get-SplunkSearchJob { <# .SYNOPSIS Script to retrieve detailed information about a submitted search job .DESCRIPTION This script will retrieve detailed information about a search job you (or someone) has submitted. It does not retrieve any gathered results of the job. .PARAMETER sid This is the sid of the job. You can use Get-SplunkSearchJobList to locate your job and get the sid. The sid is also given when you run Start-SplunkSearch. .EXAMPLE Get-SplunkSearchJobList -Filter "*4740*" | Select-Object -First 1 | Get-SplunkSearchJob This example will use Get-SplunkSearchJobList to find any jobs with the text 4740 (user locked out) in the name field--name field is always the text of the full search. If there are multiple returns it will filter to only the first one, then retrieve the detailed information from that job. .EXAMPLE Get-SplunkSearchJob -sid 123456789.12345 This will retrieve the job information for the above sid. .NOTES Author: Martin Pugh Twitter: @martin9700 Spiceworks: Martin9700 Blog: www.thesurlyadmin.com Changelog: 02/27/21 Initial Release .LINK https://github.com/martin9700/PSSplunkSearch #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)] [string]$sid ) Begin { ValidateSplunk Write-Verbose -Message "Starting Get-SplunkSearchJob" } Process { $Splat = @{ Uri = "/services/search/jobs/$sid" } $Result = Invoke-SplunkMethod @Splat $Data = $Result | Select-Object -ExpandProperty entry | Select-Object Id,Updated,Published,Author,Name $Content = $Result.entry.content $Properties = $Content | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name ForEach ($Property in $Properties) { $Data | Add-Member -MemberType NoteProperty -Name $Property -Value $Content.$Property } Write-Output $Data } } Function Get-SplunkSearchJobList { <# .SYNOPSIS This will list all of the jobs currently stored on the Splunk server (whether they are running or not). .DESCRIPTION Retrieve a list of jobs. Output is limited to just enough to identify the job from the search criteria. Results can be piped into Get-SplunkSearchJob to get more detailed information about the job. .PARAMETER Filter Use this parameter to filter the results as needed. Supports wildcards. .EXAMPLE Get-SplunkSearchJobList -Filter "*4740*" This example will use Get-SplunkSearchJobList to find any jobs with the text 4740 (user locked out) in the name field--name field is always the text of the full search. .NOTES Author: Martin Pugh Twitter: @martin9700 Spiceworks: Martin9700 Blog: www.thesurlyadmin.com Changelog: 02/27/21 Initial Release .LINK https://github.com/martin9700/PSSplunkSearch #> [CmdletBinding()] Param ( [Parameter(Mandatory=$false, Position=0)] [string]$Filter ) Begin { ValidateSplunk Write-Verbose -Message "Starting Get-SplunkSearchJobList" } Process { $Splat = @{ Uri = "/services/search/jobs" ErrorAction = "Stop" } $Data = Invoke-SplunkMethod @Splat | Select-Object -ExpandProperty entry | Select-Object @{Name = "sid";Expression={ $_.content.sid}}, @{Name = "Published";Expression={ Get-Date $_.published }}, @{Name = "Name"; Expression={ $_.name}} If ($Filter) { $Data = $Data | Where-Object Name -like $Filter } Write-Output $Data } } Function Invoke-SplunkMethod { <# .SYNOPSIS This makes the API calls to Splunk .DESCRIPTION Used by most of the functions within this module as a common way to make API calls to Splunk. This has been made available to the user in case you need to do some functions not currently covered by the module. .PARAMETER Uri The Connect-Splunk function saves the server name and port of the Splunk server, so this would be the full path after that. Example: Full path: https://splunk.yourdomain.com:8089/services/search/jobs You would enter: /services/search/jobs .PARAMETER Body Hashtable of parameters needed by the API endpoint. Make sure to follow the case set out in the Splunk API reference, Splunk is case sensitive. Example: @{ offset = 50 count = 50 } .PARAMETER Method Web method for the API endpoint. Must be "GET","POST","PUT" or "DELETE". GET is the default. .EXAMPLE Invoke-SplunkMethod -Uri "/services/search/jobs" This would do a GET call to the "/services/search/jobs" endpoint. .NOTES Author: Martin Pugh Twitter: @martin9700 Spiceworks: Martin9700 Blog: www.thesurlyadmin.com Changelog: 02/27/21 Initial Release .LINK https://github.com/martin9700/PSSplunkSearch #> [CmdletBinding()] Param ( [string]$Uri, [hashtable]$Body, [ValidateSet("GET","POST","PUT","DELETE")] [string]$Method = "GET" ) Process { If ($Uri[0] -ne "/") { $Uri = "/$Uri" } $Uri = "$($Uri)?output_mode=json" $RestSplat = @{ Uri = "$($Script:SplunkConnect.BaseUri)$Uri" Header = $Script:SplunkConnect.Header Method = $Method Body = $Body UseBasicParsing = $true Verbose = $false ErrorAction = "Stop" } Try { $Response = Invoke-RestMethod @RestSplat } Catch { Write-Error "Error retrieving query: $_" Return } # paging Return $Response } } Function Receive-SplunkSearch { <# .SYNOPSIS Use this to retrieve results from your completed Splunk search .DESCRIPTION This function is used to retrieve the search results from the designated search you created. .PARAMETER sid This is the sid associated with your search job. .PARAMETER ReceiveCount Default is 250 items. With larger queries you can get hundreds, if not thousands of results. To not kill your Splunk server this function limits the number items that can be retrieved by any single API call. This is being done in the background and you will get all results as output of this function. .EXAMPLE Start-SplunkSearch -Query "EventCode=4740" | Wait-SplunkSearch | Receive-SplunkSearch Starts a search looking for event id 4740, waits for the job to complete and then retrieves the results .EXAMPLE Get-SplunkSearchJobList -Filter "*4740*" | Receive-SplunkSearch .EXAMPLE Receive-SplunkSearch -sid 123456789.12345 .NOTES Author: Martin Pugh Twitter: @martin9700 Spiceworks: Martin9700 Blog: www.thesurlyadmin.com Changelog: 02/27/21 Initial Release .LINK https://github.com/martin9700/PSSplunkSearch #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)] [string]$sid, [int]$ReceiveCount = 250 ) Begin { ValidateSplunk Write-Verbose -Message "Starting Receive-SplunkSearch" } Process { $GetJob = Get-SplunkSearchJob -sid $sid $Top = 0 If ($GetJob.resultCount -gt $ReceiveCount) { $Top = [math]::Ceiling($GetJob.resultCount / $ReceiveCount) - 1 } $OffsetCount = 0 $Data = ForEach ($Offset in (0..$Top)) { If ($Offset -gt 0) { $OffsetCount += $ReceiveCount } $RetrieveSplat = @{ Uri = "/services/search/jobs/$sid/results" Body = @{ count = $ReceiveCount offset = $OffsetCount } } Invoke-SplunkMethod @RetrieveSplat | Select-Object -ExpandProperty results } $Data | Add-Member -MemberType ScriptProperty -Name "Date" -Value { Get-Date $this._time } Write-Output $Data } } Function Remove-SplunkSearch { <# .SYNOPSIS Delete the search job .DESCRIPTION When you've retrieved the search results needed, you can remote the search and it's results from the server using this function. .PARAMETER sid This is the sid associated with your search job. .EXAMPLE Remove-SplunkSearch -sid 123456789.12345 Removes the specified search job. .EXAMPLE Get-SplunkSearchJobList -Filter "*4740*" | Remove-SplunkSearch .NOTES Author: Martin Pugh Twitter: @martin9700 Spiceworks: Martin9700 Blog: www.thesurlyadmin.com Changelog: 02/27/21 Initial Release .LINK https://github.com/martin9700/PSSplunkSearch #> [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")] Param ( [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [string]$sid, [switch]$Force ) Begin { ValidateSplunk Write-Verbose -Message "Starting Remove-SplunkSearch" } Process { $Job = Get-SplunkSearchJob -sid $sid -ErrorAction Stop $DeleteSplat = @{ Uri = "/services/search/jobs/$sid" Method = "DELETE" ErrorAction = "Stop" } If ($Force -or $PSCmdlet.ShouldProcess("Remove this Splunk job?", $Job.Name)) { Invoke-SplunkMethod @DeleteSplat } } } Function Start-SplunkSearch { <# .SYNOPSIS Start a search job on your Splunk server .DESCRIPTION This will start a search job on your Splunk server, returning just the sid number of that job. Use Wait-SplunkSearch to watch the job until it finishes, and then Receive-SplunkSearch to retrieve the results. .PARAMETER Query This is the query (using Splunk's query language) for your search .PARAMETER Start Start time of your search. To keep you from overwhelming your server this defaults to 1 day ago. .PARAMETER End End time of your search. By default this will be now. .PARAMETER Index Specify the index you wish to search. This is an optional parameter and you could include the index in your Query if you wanted to. .EXAMPLE Remove-SplunkSearch -sid 123456789.12345 Removes the specified search job. .EXAMPLE Get-SplunkSearchJobList -Filter "*4740*" | Remove-SplunkSearch .NOTES Author: Martin Pugh Twitter: @martin9700 Spiceworks: Martin9700 Blog: www.thesurlyadmin.com Changelog: 02/27/21 Initial Release .LINK https://github.com/martin9700/PSSplunkSearch #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] Param ( [Parameter(Mandatory=$true)] [string]$Query, [Parameter(Mandatory=$false)] [datetime]$Start = ((Get-Date).AddDays(-1)), [Parameter(Mandatory=$false)] [datetime]$End = (Get-Date), [Parameter(Mandatory=$false)] [string]$Index ) Begin { ValidateSplunk Write-Verbose -Message "Starting Start-SplunkSearch" } Process { $Search = $Query If ($Index -and $Search -notmatch "index ?= ?") { $Search += " index=$Index" } $Body = @{ search = "search $Search" earliest_time = Get-Date $Start.ToUniversalTime() -Format "yyyy-MM-ddTHH:mm:ss" latest_time = Get-Date $End.ToUniversalTime() -Format "yyyy-MM-ddTHH:mm:ss" } $SearchSplat = @{ Uri = "/services/search/jobs" Body = $Body Method = "POST" } $Data = Invoke-SplunkMethod @SearchSplat [PSCustomObject]@{ sid = $Data.sid Search = $Body.Search Earliest_Time = $Body.earliest_time Latest_Time = $Body.latest_time } } } Function ValidateSplunk { <# .SYNOPSIS Helper script to make sure a connection variable has been created #> [CmdletBinding()] Param () If (-not (Get-Variable SplunkConnect -Scope Script -ErrorAction SilentlyContinue)) { Write-Error "You have not connected to Splunk, please run Connect-Splunk" -ErrorAction Stop } ElseIf ($Script:SplunkConnect.Expires -lt (Get-Date)) { Write-Error "Your Splunk connection has expired, please run Connect-Splunk again" -ErrorAction Stop Disconnect-Splunk } } Function Wait-SplunkSearch { <# .SYNOPSIS Wait for a search result job to finish .DESCRIPTION You can use this function to watch a running search job until it finishes. .PARAMETER sid This is the sid associated with your search job. .EXAMPLE Start-SplunkSearch -Query "EventCode=4740" Index="domain_controllers" -Start "2/20/21" -End "2/22/21" | Wait-SplunkSearch Begins a search and waits for it to complete. .EXAMPLE Get-SplunkSearchJobList -Filter "*4740*" | Wait-SplunkSearch If this filter returns multiple results, it will wait for the first one before moving on to the second and so on. .NOTES Author: Martin Pugh Twitter: @martin9700 Spiceworks: Martin9700 Blog: www.thesurlyadmin.com Changelog: 02/27/21 Initial Release .LINK https://github.com/martin9700/PSSplunkSearch #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [string]$sid ) Begin { ValidateSplunk Write-Verbose -Message "Starting Wait-SplunkSearch" } Process { $SearchSplat = @{ Uri = "/services/search/jobs/$sid" Method = "GET" ErrorAction = "Stop" } $Wait = 0 Do { Start-Sleep -Seconds $Wait $GetJob = Invoke-SplunkMethod @SearchSplat If (-not $Wait) { $Start = Get-Date $GetJob.entry.content.request.earliest_time -Format "MM/dd/yyyy HH:mm:ss" $End = Get-Date $GetJob.entry.content.request.latest_time -Format "MM/dd/yyyy HH:mm:ss" Write-Verbose "Title: $($GetJob.entry.content.request.search) Start: $Start End: $End" $Wait = 8 } Write-Verbose "Job ($sid) status is $($GetJob.entry.content.dispatchState) ($($GetJob.entry.content.runDuration))" } Until ($GetJob.entry.content.isDone) [PSCustomObject]@{ sid = $sid Name = $GetJob.entry.content.request.search Earliest_Time = $Start Latest_Time = $End RunDuration = $GetJob.entry.content.runDuration Status = $GetJob.entry.content.dispatchState } } } |