Numlock On Startup

Numlock On Startup

I hate it when I start up my PC and my number lock is turned off. Did you know you can set this to be automatic. Yep that’s right, automatic. Start up powershell as administrator and run the single line of code below. Then you should be set to go.

Set-Itemproperty -Path 'HKU:\.DEFAULT\Control Panel\Keyboard\' -Name 'InitialKeyboardIndicators' -Value '2'

Use wisely fellow admins.

Invoke-SHDMoveFiles

Invoke-SHDMoveFiles

Ever need a service that copies a single folder to multiple locations at once? This script will do just that. It will copy a single location to more than one location and even log the outcomes accordingly. Thus, you will be able to set this one up as a task and run it every so often.

Invoke-SHDMoveFiles {
    <#
    .SYNOPSIS
        Copy files from target source folders to a set of target folders.
    .DESCRIPTION
        Copys from multiple source folders to a select target folders. This allows archiving to occur.
        The folders are not auto created. Thus, the folders must exists. The script also adds a log file of any error
        messages you see. See notes for more details.
    .PARAMETER SourceFolder
        [String[]] SourceFolder is an Array of Strings of target folders to move the files from.
    .PARAMETER TargetFolder
        [String[]] TargetFolder is an Array of strings where the files will be copied to. This is not a one for one ratio. Thus all files will exist inside the targetfolder.
    .EXAMPLE
        ./Invoke-SHDMoveFiles -SourceFolder 'C:\tbc\Tmp\Source Folder\','C:\tbc\tmp\Source Folder 2\' -TargetFolder 'C:\tbc\tmp\Target Folder\','C:\tbc\tmp\Target Folder 2\'
        Moves all files from the source folder 1 and 2 to target folders 1 and 2.
    .EXAMPLE
        ./Invoke-SHDMoveFiles -SourceFolder 'C:\tbc\Tmp\Source Folder\' -TargetFolder 'C:\tbc\tmp\Target Folder\','C:\tbc\tmp\Target Folder 2\'
        Moves all files from the source folder 1 to target folders 1 and 2.
    .EXAMPLE
        ./Invoke-SHDMoveFiles -SourceFolder 'C:\tbc\Tmp\Source Folder 1\','C:\tbc\Tmp\Source Folder 2\','C:\tbc\Tmp\Source Folder 3\'  -TargetFolder 'C:\tbc\tmp\Target Folder\'
        Moves all files from the source folder 1, 2, and 3 to the target folder
    .EXAMPLE
        ./Invoke-SHDMoveFiles -SourceFolder 'C:\tbc\Tmp\Source Folder\','C:\tbc\tmp\Source Folder 2\' -TargetFolder 'C:\tbc\tmp\Target Folder\','C:\tbc\tmp\Target Folder 2\' -Recurse
        Moves all files under Source folder 1 and 2 to target folders 1 and 2.
    .EXAMPLE
        ./Invoke-SHDMoveFiles -SourceFolder 'C:\tbc\Tmp\Source Folder\' -TargetFolder 'C:\tbc\tmp\Target Folder\','C:\tbc\tmp\Target Folder 2\' -Recurse
        Moves all files under source folder 1 to target folders 1 and 2.
    .EXAMPLE
        ./Invoke-SHDMoveFiles -SourceFolder 'C:\tbc\Tmp\Source Folder 1\','C:\tbc\Tmp\Source Folder 2\','C:\tbc\Tmp\Source Folder 3\'  -TargetFolder 'C:\tbc\tmp\Target Folder\' -Recurse
        Moves all files under source folder 1, 2, and 3 to the target folder
    .INPUTS
        [String] Folder Paths
    .OUTPUTS
        No Output
    .NOTES
        Author: David Bolding
        Date: 8/16/2020
        Purpose: Moving files around and making archives
        Min Powershell Version: 4

        This command generates error logs located c:\tbc\SHDMoveFileLog.txt. The error logs structure is broken up in 4 sections seperated with by ;
        1;$((Get-Date).tostring());Catch;$($_.Exception)
        1 - The placement of the error physically inside the script.
        2 - The date and time of the error
        3 - What type of error
        4 - Information about the error

        Run this command inside a scheduled task every 5 minutes to copy files needed to required locations. Make sure the
        tasks running user has full access right to the file locations provided.
    .LINK
        https://bolding.us
    #>
    [cmdletbinding()]
    param (
        [Parameter(HelpMessage = "Source Folder", Mandatory = $True)][string[]]$SourceFolder,
        [Parameter(HelpMessage = "Target Folders", Mandatory = $True)][string[]]$TargetFolder,
        [parameter(HelpMessage = "Recurse the Source Folder")][switch]$Recruse
        [parameter(HelpMessage = "Log Location")][string]$Logs = "C:\tmp\SHDMoveFileLog.txt"
    )
    #Tests to see if local c:\tbc exists, if it doesn't, it will create the path.
    if (Test-Path (Split-Path $logs)) { "" >> $logs } else { mkdir (Split-Path $logs) }

    #Start with the first source
    foreach ($Source in $SourceFolder) {

        #Tests to see if the source path exists.
        if (Test-Path -Path $Source) {

            #Grabs the required files from the first source path. It only grabs files.
            #Also checks if there is a recruse flag. If so, recurses accordingly.
            if ($Recruse) { $files = Get-childitem -Path $Source -File -Recurse } else { $files = Get-childitem -Path $Source -File }

            #Next we sort through the files in question
            foreach ($File in $files) {

                #Create a test bool.
                $success = $false

                #Starts the target folder sorting
                foreach ($Target in $TargetFolder) {

                    #Tests to see if target folder exists. If not, logs.
                    if (Test-Path -Path $target) {

                        #Enter a try catch to copy the files
                        try {

                            #Copys a single file to target folder. Overwrites any other there without confirmation
                            #No Confiramtion due to the lack of human Interaction.
                            Copy-Item -Path $file.fullname -Destination $Target -Force -Confirm:$false

                            #If no error so far, sets the success bool to true
                            $success = $true
                        }
                        catch {

                            #a failure occured, thus we set the success bool to false
                            $success = $false

                            #We log the error. This is the first log that shows up in the system.
                            #We date it.
                            #We state it's a catch
                            #Then we give the reason the try catch gives.
                            "1;$((Get-Date).tostring());Catch;$($_.Exception)" >> $logs
                        }
                    }
                    else {
                        #We log the fact that we can't reach the target location
                        "2;$((Get-Date).tostring());CanNotFind;$Target" >> $logs
                    }
                }
                #We test the bool for success.
                if ($success -eq $true) {
                    try {

                        #if successful we remove the file.
                        Remove-Item -Path $file.FullName -Force -Confirm:$false
                    }
                    catch {

                        #If we can't remove the file we log the reason why.
                        "3;$((Get-Date).tostring());Catch;$($_.Exception)" >> $logs
                    }
                }
            }
        }
        else {
            #We log the fact we can't reach the source location.
            "4;$((Get-Date).tostring());CanNotFind;$Source" >> $logs
        }
    }
}

Examples

./Invoke-SHDMoveFiles -SourceFolder 'C:\tbc\Tmp\Source Folder\' -TargetFolder 'C:\tbc\tmp\Target Folder\','C:\tbc\tmp\Target Folder 2\'

Moves all files from the source folder 1 to target folders 1 and 2. This is good for a backup of files. What I use this for is a custom software at work. It other products drop to location 1. Then it has a backup folder and the system folder. There we go, good as gold.

./Invoke-SHDMoveFiles -SourceFolder 'C:\tbc\Tmp\Source Folder 1\','C:\tbc\Tmp\Source Folder 2\','C:\tbc\Tmp\Source Folder 3\'  -TargetFolder 'C:\tbc\tmp\Target Folder\' -Logs c:\tmp\Combiner.txt

Moves all files from the source folder 1, 2, and 3 to the target folder. It also logs any errors. This is useful when trying to come folders together.

Notes

  • This script was designed to be ran as a task. If it doesn’t grab a file the first time around, it will grab it the second time around. It does this by using the try catch. It trys to move a file. if it doesn’t, it logs it and leaves it. We do that because a lot of times these files are being created and have a lock state. It’s best not to move them during that state.
  • This script auto logs into the c:\tmp location if you don’t state otherwise when things go wrong.

Invoke File Service Integrity Check

Invoke File Service Integrity Check

This little script will allow you to check a folder location, if that folder location has x number of files, it will restart the service of your choice on a target computer. Then it will log that information and finally it will email someone this information.

function Invoke-FileServiceIntegrityCheck {
    <#
    .SYNOPSIS
        Checks a file path for number of files. If greater, restarts a service and emails a message and logs a log.
    .DESCRIPTION
        Checks a file path for a set number of files that are predefined by the end user. If the file count is greater, it will restart a service. It will log the results and email a person stated by the end user.
    .PARAMETER SearchPath
        Target Path to scan for file count.
    .PARAMETER FileCount
        File count that will be used to determine the request size.
    .PARAMETER ComputerName
        Target Computer Name that the service lives on.
    .PARAMETER ServiceName
        Name of the service to restart.
    .PARAMETER SendReportTo
        The email address to which the email report will be sent to.
    .PARAMETER EmailDomain
        The domain name. For example, example.local
    .PARAMETER MailServer
        The mail server to send the email from.
    .PARAMETER Recurse
        Recurses the file search path.
    .PARAMETER Credential
        The Credentials, if needed for the email server.
    .EXAMPLE
        Invoke-FileServiceIntegrityCheck -SearchPath \\server1\share -FileCount 25 -ComputerName server2 -ServiceName intergrator -SendReportTo bob@example.com -EmailDomain example.com -Mailserver mail.example.com -Credential (Get-Credential)

        Checks the folder \\server1\share to see if there is 25 or more files. If it does, it will restart the service named intergrator on server 2.
        Then it will send an bob@example.com from watcheye@example.com using mail.example.com and the credentials provided.
    .EXAMPLE
        Invoke-FileServiceIntegrityCheck -SearchPath \\server1\share -FileCount 25 -ComputerName server2 -ServiceName intergrator -SendReportTo bob@example.com -EmailDomain example.com -Mailserver mail.example.com

        Checks the folder \\server1\share to see if there is 25 or more files. If it does, it will restart the service named intergrator on server 2.
        Then it will send an bob@example.com from watcheye@example.com using mail.example.com. Email will be sent from the server. If no relay is setup, the server will reject the email.
    .INPUTS
        [none]
    .OUTPUTS
        email and log
    .NOTES
        Author: David Bolding
        Date: 10/14/2020

    .LINK
        https://github.com/rndadhdman/PS_Super_Helpdesk
    #>
    [cmdletbinding()]
    param (
        [parameter(HelpMessage = "Folder Search Path", Mandatory = $True)][string]$SearchPath,
        [parameter(HelpMessage = "File Count", Mandatory = $True)][int]$FileCount,
        [parameter(HelpMessage = "Computer That the server Lives", Mandatory = $True)][String]$ComputerName,
        [parameter(HelpMessage = "Service Name", Mandatory = $True)][String]$ServiceName,
        [parameter(HelpMessage = "Service Name", Mandatory = $True)][String]$SendReportTo,
        [parameter(HelpMessage = "Service Name", Mandatory = $True)][String]$EmailDomain,
        [parameter(HelpMessage = "Service Name", Mandatory = $True)][String]$Mailserver,
        [parameter(HelpMessage = "Recurse the Search")][switch]$Recurse
        [Parameter(HelpMessage = "Allows for custom Credential.")][System.Management.Automation.PSCredential]$Credential
    )

    #Checks Files
    if ($Recurse) { $Files = Get-ChildItem "$SearchPath" -Recurse } else { $Files = Get-ChildItem "$SearchPath" }


    if ($Files.Count -ge $FileCount) {

        $StopTime = Get-date
        Get-Service -ComputerName mtxs-lifetecapp -Name "Interlink" | Stop-Service

        sleep 30

        Get-Service -ComputerName mtxs-lifetecapp -Name "Interlink" | Start-Service
        $StartTime = Get-date

        sleep 30

        if ($Recurse) { $Files2 = Get-ChildItem "$SearchPath" -Recurse } else { $Files2 = Get-ChildItem "$SearchPath" }

        if (!(Test-Path c:\logs)) {mkdir c:\logs}

        #Gathers Log Info
        $Log = [pscustomobject]@{
            ComputerName = $ComputerName
            ServiceName = $ServiceName
            SearchPath = $SearchPath
            StopFileCount = $Files.count
            ServiceStopTime = $StopTime.ToString()
            ServiceStartTime = $StartTime.tostring()
            StartFileCount = $Files2.count
            CallingComputer = $env:COMPUTERNAME
        }

        $Log | Export-csv c:\temp\Invoke_FileServiceIntegrityCheck_log.csv -Append

        #Builds EMail
        $ErrorHtmlbody = @"
<html>
<head>
<style>
body {
    Color: #252525;
    font-family: Verdana,Arial;
    font-size:11pt;
}
h1 {
    text-align: center;
    color:#C8102E;
    Font-size: 34pt;
    font-family: Verdana, Arial;
}
</style>
</head>
<body>
<h1>$ServiceName Restarted</h1>
<hr>
<br>
Dear Watcher,<br>
<br>
During my last check there were <b>$($Files.Count)</b> files inside <i>$SearchPath</i>. The Integrator was stopped on <b>$($StopTime.tostring())</b> and started on <b>$($StartTime.tostring())</b>. There are now <b>$($files2.count)</b> in <i>$SearchPath</i>. Please investigate on why the $ServiceName failed on $ComputerName.<br>
<br>
<br>
Thank you,<br>
<i>$ServiceName WatchEye</i><br>
<br>
<hr>
</body>
</html>
"@
        if ($PSBoundParameters.ContainsKey('Credential')) {
            Send-MailMessage -To $SendReportTo -From "Service Watch Eye <WatchEye@$EmailDomain>" -Subject "$ServiceName Failure" -BodyAsHtml $ErrorHtmlbody -SmtpServer $Mailserver -Credential $Credential
        } else {
            Send-MailMessage -To $SendReportTo -From "Service Watch Eye <WatchEye@$EmailDomain>" -Subject "$ServiceName Failure" -BodyAsHtml $ErrorHtmlbody -SmtpServer $Mailserver
        }
    }

}

Example

Invoke-FileServiceIntegrityCheck -SearchPath \\server1\share -FileCount 25 -ComputerName server2 -ServiceName integrator -SendReportTo bob@example.com -EmailDomain example.com -Mailserver mail.example.com

In this example, we will search for the share on server1 if it has more than 25 files. If it does, then we will restart the integrator on server2. Finally, we will email bob@example.com with an email from watcher@example.com using the mail server mail.example.com.

Copy A User’s Group to Other Users

Copy A User’s Group to Other Users

Often times I am asked to give user a the same rights as user b. Since everything is setup with groups, this is easy to do. All I have to do is copy all the security groups to the new users. Normally I would use the command Get-ADPrincipalGroupMembership whoever, that has become less trust worthy over time for us. So I made a different approach.

$Groups = (Get-ADUser -Identity $SourceUser -Properties memberof).memberof -Replace '^cn=([^,]+).+$', '$1' | Sort-Object | ForEach-Object { (Get-ADGroup -Filter "name -like '$_'")}

The above command grabs the source users member of and filters it out for just the name. Then it loops through each and gets the group name. From here we can filter even farther to get the security and then loop those and added the single member making a one liner, but I want this thing to be bigger and better. A good function that can do distribution, security, universal, global and domain local groups to more than one single user with credentials or not. I want this thing to be nice. Here is the heart beat of the command:

foreach ($Target in $TargetUser) {
        $Groups | ForEach-Object {
            try {
                if ($PSBoundParameters.ContainsKey('Credential')) {
                    Write-Verbose "Add $($_.Samaccountname) a $($_.GroupCategory.tostring()) a $($_.GroupScope.tostring()) to $Target"
                    Add-ADGroupMember -Identity $_.samaccountname -Members $Target -Credential $Credential
                } else {
                    Write-Verbose "Add $($_.Samaccountname) a $($_.GroupCategory.tostring()) a $($_.GroupScope.tostring()) to $Target"
                    Add-ADGroupMember -Identity $_.samaccountname -Members $Target
                }
            } catch {
                Write-Verbose "Failed to add $($_.Samaccountname) to $Target"
                Write-Warning "$_ could not apply to $Target"
            }
        }
    }

We target each user with this information. Then we add the group accordingly. So, how do you split the Security group vs Distribution group? We do this by using PSboundparameters and a Where-object group. So we declare a parameter in the parameter area and then ask if it is being used. If it is, we use the where object to search for the groupcategory.

$Groups = (Get-ADUser -Identity $SourceUser -Properties memberof).memberof -Replace '^cn=([^,]+).+$', '$1' | Sort-Object | ForEach-Object { (Get-ADGroup -Filter "name -like '$_'")}
    if ($PSBoundParameters.ContainsKey('GroupCategory')) {$Groups = $Groups | Where-Object {$_.GroupCategory -like "$GroupCategory"}}
    if ($PSBoundParameters.ContainsKey('GroupScope')) {$Groups = $Groups | Where-Object {$_.GroupScope -like "$GroupScope"}}

Lets put it all together shall we, so you can get back on with your day.

Function Copy-SHDUserGroupToUser {
    [cmdletbinding()]
    param (
        [Parameter(
            ValueFromPipeline = $True,
            ValueFromPipelineByPropertyName = $True,
            HelpMessage = "Enter a users Name",
            Mandatory = $true)][String]$SourceUser,
        [Parameter(HelpMessage = "Target User", Mandatory = $True)][string[]]$TargetUser,
        [parameter(Helpmessage = "Group Category")][validateset("Security","Distribution")]$GroupCategory,
        [parameter(Helpmessage = "Group Scope")][validateset("Universal","Global","DomainLocal")]$GroupScope,
        [Parameter(HelpMessage = "Allows for custom Credential.")][System.Management.Automation.PSCredential]$Credential
    )
    $Groups = (Get-ADUser -Identity $SourceUser -Properties memberof).memberof -Replace '^cn=([^,]+).+$', '$1' | Sort-Object | ForEach-Object { (Get-ADGroup -Filter "name -like '$_'")}
    if ($PSBoundParameters.ContainsKey('GroupCategory')) {$Groups = $Groups | Where-Object {$_.GroupCategory -like "$GroupCategory"}}
    if ($PSBoundParameters.ContainsKey('GroupScope')) {$Groups = $Groups | Where-Object {$_.GroupScope -like "$GroupScope"}}
    foreach ($Target in $TargetUser) {
        $Groups | ForEach-Object {
            try {
                if ($PSBoundParameters.ContainsKey('Credential')) {
                    Write-Verbose "Add $($_.Samaccountname) a $($_.GroupCategory.tostring()) a $($_.GroupScope.tostring()) to $Target"
                    Add-ADGroupMember -Identity $_.samaccountname -Members $Target -Credential $Credential
                } else {
                    Write-Verbose "Add $($_.Samaccountname) a $($_.GroupCategory.tostring()) a $($_.GroupScope.tostring()) to $Target"
                    Add-ADGroupMember -Identity $_.samaccountname -Members $Target
                }
            } catch {
                Write-Verbose "Failed to add $($_.Samaccountname) to $Target"
                Write-Warning "$_ could not apply to $Target"
            }
        }
    }
} 

Examples

Copy-SHDUserGroupToUser -SourceUser bobtheuser -TargetUser test1,test2,test3 -GroupCategory Security -GroupScope Global -Verbose

This will add all of bobtheuser’s global security groups to test1, test2, and test3 users and verbose the info out.

Copy-SHDUserGroupToUser -SourceUser bobtheuser -TargetUser test1,test2,test3 

This will add all the bobtheuser’s groups to test1, test2, and test3.

Get Lock Out Info From DC

Get Lock Out Info From DC

Have you ever had trouble getting lockout information for a single user? The DCs have the logs, but getting them can be tough. The core idea is to use the Get-Winevent command to gather the logs needed. Doing this from a remote computer is time-consuming. However, using the Invoke-Command tends to speed things up. Inside this example, I am going to use something called Splatting. Splatting allows you to drop everything a command needs into a command quickly and easily by creating a hash table. Now, not all commands like splatting, so be aware of that. Here is the heart of the code:

$parameters = @{
        ComputerName = $ComputerName
        ScriptBlock  = {
            Param ($param1)
            $FilterTable = @{
                'StartTime' = $param1
                'EndTime'   = (Get-date)
                'LogName'   = 'Security'
                'Id'        = 4740
            }
            $Events = Get-WinEvent -FilterHashtable $FilterTable
            foreach ($E in $Events) {
                [pscustomobject]@{
                    Time     = $E.TimeCreated
                    ID       = $E.ID 
                    DC       = $E.Properties[4].value
                    SID      = $E.Properties[2].value
                    Domain   = $E.Properties[5].Value
                    Username = $E.Properties[0].value
                    Computer = $E.Properties[1].value
                }
            }
        }
        ArgumentList = $time           
    }
Invoke-command @Parameters

The first thing we are doing is creating the parameters that will be splatted. Just like a hashtable, it’s something = something. So the computer name is the Array computername. The computer name inside the Invoke-command can handle arrays. Making this much easier as we want the DCs.

Next is the script block. This is where the invoke-command will be executing everything from. We start off with a Param () block. It will get it’s input from the Argumentlist later. The Argumentlist is the variables we will be dropping into the command. In this case, it will be time. As in this script you can build your time based on how far back you want to search your logs.

Next is the filter table. The Get-winevent command likes hashtables. So we start off with our starttime, basically how far back we are going to go. Then the end time which is the time of execution. The Logname is the type, there is an application, security, system, and more. Since the lockouts are security, we choose security. Finally, we want the ID. For account lockouts, the ID is 4740. There is an online Encyclopedia for windows logs. (Link)

The Get-WinEvent command is followed by the FilterHashtable flag. We dump this into the events variable for sort throu. Now we search each Event in Events. We express this with an E because $event is a system variable and we don’t want to cause any issues. We want the time it was created. The ID, what DC it came from. The SID of the user. The Domain. The username of the user, and finally we want the calling workstation. Once all that is put together we splat it into the command Invoke-Command.

That is the heart, but not the power. This script allows you to call back from the day, hour, and minute back. If you don’t put in a input, it makes it a day for you. It also can target a single user or different computers. Finally it gives you the ability to use different credentials. Lets take a look at the parameters.

param (
        [parameter(Helpmessage = "Username of target")][Alias('User', 'Samaccountname')][String[]]$Username,    
        [parameter(Helpmessage = "Days back")][int]$Day,
        [parameter(Helpmessage = "Days back")][int]$Hour,
        [parameter(Helpmessage = "Days back")][int]$Minute,
        [parameter(Helpmessage = "Computers to target, default is domain controllers.")][Alias('Computer', 'PC')][String[]]$ComputerName = (Get-ADDomain).ReplicaDirectoryServers,
        [Parameter(HelpMessage = "Allows for custom Credential.")][System.Management.Automation.PSCredential]$Credential
    )

We first are looking for a list of usernames. Notice we don’t have to declare this variable. So, if we just want all the lockouts, we just don’t delcare it. This is done by using the PSBoundParameters.ContainsKey option. So, if the script sees the username is being used, it will follow this command.

if ($PSBoundParameters.ContainsKey('Username')) {
        foreach ($user in $Username) {
            $Return | Where-Object { $_.Username -like "$user" } | Sort-Object Time | Select-Object Username, Domain, SID, Time, Computer, PSComputerName
        }
    }

The next parameters are also optional. It is days, hour, and minutes back. So, you can state 1 day, 3 hours, and 2 minutes ago and it will find the times during starting at that moment forwarder. This is good if you plan to use this script as an auditor (what it was built for). How this is achived once again is the use of psboundparameters.containskey.

$time = Get-date
if ($PSBoundParameters.ContainsKey('Day')) { $time = ($time.AddDays( - ($day))) } 
    if ($PSBoundParameters.ContainsKey('Hour')) { $time = ($time.AddHours( - ($Hour))) } 
    if ($PSBoundParameters.ContainsKey('Minute')) { $time = ($time.AddMinutes( - ($Minute))) } 
    if (!($PSBoundParameters.ContainsKey('Day')) -and !($PSBoundParameters.ContainsKey('Hour')) -and !($PSBoundParameters.ContainsKey('Minute'))) {
        $time = $time.AddDays( - (1))
    }

We first declare the time with the current time/date. Then based on the input, we remove days, hours, or minutes from the current time. If there is no input, we tell time to remove 1 day. Depending on the size of your organization and how many logs you have, this can be useful. The $time will be used in the argumentlist.

Now we have the Computername parameter. If you have differentiating computers than your DCs that handles these logs, you can target them with this command. Otherwise, we grab the dc information with a single line of code.

(Get-ADDomain).ReplicaDirectoryServers

Finally we have the ability to add the credentials. Once you delcare the credentials, we add the credential flag to our splat. To do this we create a hashtable with a single item and add it to the parameter.

if ($PSBoundParameters.ContainsKey('Credential')) {
        $parameters += @{Credential = $Credential }
    }

Now, lets put all this together in a single script so you can copy it and move along with your day.

Function Get-SHDLockoutInfo {
    [cmdletbinding()]
    param (
        [parameter(Helpmessage = "Username of target")][Alias('User', 'Samaccountname')][String[]]$Username,    
        [parameter(Helpmessage = "Days back")][int]$Day,
        [parameter(Helpmessage = "Days back")][int]$Hour,
        [parameter(Helpmessage = "Days back")][int]$Minute,
        [parameter(Helpmessage = "Computers to target, default is domain controllers.")][Alias('Computer', 'PC')][String[]]$ComputerName = (Get-ADDomain).ReplicaDirectoryServers,
        [Parameter(HelpMessage = "Allows for custom Credential.")][System.Management.Automation.PSCredential]$Credential
    )
    $time = Get-date
    if ($PSBoundParameters.ContainsKey('Day')) { $time = ($time.AddDays( - ($day))) } 
    if ($PSBoundParameters.ContainsKey('Hour')) { $time = ($time.AddHours( - ($Hour))) } 
    if ($PSBoundParameters.ContainsKey('Minute')) { $time = ($time.AddMinutes( - ($Minute))) } 
    if (!($PSBoundParameters.ContainsKey('Day')) -and !($PSBoundParameters.ContainsKey('Hour')) -and !($PSBoundParameters.ContainsKey('Minute'))) {
        $time = $time.AddDays( - (1))
    }
    $parameters = @{
        ComputerName = $ComputerName
        ScriptBlock  = {
            Param ($param1)
            $FilterTable = @{
                'StartTime' = $param1
                'EndTime'   = (Get-date)
                'LogName'   = 'Security'
                'Id'        = 4740
            }
            $Events = Get-WinEvent -FilterHashtable $FilterTable
            foreach ($E in $Events) {
                [pscustomobject]@{
                    Time     = $E.TimeCreated
                    ID       = $E.ID 
                    DC       = $E.Properties[4].value
                    SID      = $E.Properties[2].value
                    Domain   = $E.Properties[5].Value
                    Username = $E.Properties[0].value
                    Computer = $E.Properties[1].value
                }
            }
        }
        ArgumentList = $time           
    }
    if ($PSBoundParameters.ContainsKey('Credential')) {
        $parameters += @{Credential = $Credential }
    }
    $Return = Invoke-Command @parameters 
    if ($PSBoundParameters.ContainsKey('Username')) {
        foreach ($user in $Username) {
            $Return | Where-Object { $_.Username -like "$user" } | Sort-Object Time | Select-Object Username, Domain, SID, Time, Computer, PSComputerName
        }
    }
    else {
        $Return | Sort-Object UserName | Select-Object Username, Domain, SID, Time, Computer, PSComputerName
    }
} 

I hope you enjoy this little script and I hope it helps you grow your company. If you find any bugs, let me know. Thank you so much and have a great day!

Access PDQ Info with Powershell

Access PDQ Info with Powershell

You read that right, access PDQ information with PowerShell. You will need the PS SQL Lite module first. This module will allow you to access the SQL lite server. The second thing you will need is a Powershell script on the server accessing the module information. Then finally you will need to be able to access that script remotely. Let’s install the PSSQLite first. Do this on the PDQ server as the script that will be interacting with the SQLite database is on that server.

Install-Module -Name PSSQLite -RequiredVersion 1.0.3

Now we ill build a script on the PDQ server. The folder should be accessible by anyone you want to run this script. Let’s look at the starting points.

First we will import the module using the Import-Module command.

Import-Module PSSQLite

Next we need to declare the database. By default the database is located under the C > ProgramData > Admin Arsenal > PDQ Inventory > Database.db if it is not, you will need to declare it here.

$Database = "C:\ProgramData\Admin Arsenal\PDQ Inventory\database.db"

Before we continue, this script is designed to grab just a single computer’s information. If you choose to grab more than one computer, this can cause PDQ to crash and even the server to crash. So, play itself and don’t be too greedy. With that said, Let’s grab the computers with the computer name. from the server. This is our first SQL query of the script. It’s also the most important as it gives us the Computer ID. Without it, nothing else will work.

$Computer = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM Computers WHERE Name LIKE '$ComputerName'"

The Command to grav the SQL lite database information is Invoke-SQLiteQuery. Watch out because I made the mistake of adding an additional L while typing it. We select the data base as the data source. And the rest is very much like SQL commands. Inside our Query we select everything from the computers table where the name is like the computer name we provide. This gives us a lot of useful data that can be used else where. Once again, the ComputerID is the most important part of this data.

We will repeat this process but instead of looking for the name, we will be looking for the computerID inside our SQL Query from each table.

Apps = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM Applications WHERE ComputerID LIKE '$($Computer.ComputerID)'"

Here we are selecting everything from the Application table where the ComputerID is like $($Computer.ComputerID). Without the first command the second command would not run. From here you can do each table like this until you create a list of data you want. You can place the data inside the [pscustomobject] and pump that out for use else where. Here is a full list of items I grab.

[pscustomobject]@{
    Computer = $Computer
    CPU = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM CPUs WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    CustomInfo = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM CustomComputerValues WHERE ComputerID LIKE '%$($Computer.ComputerId)%'"                 
    Apps = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM Applications WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    PDQDeployments = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM PDQDeployments WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    DiskDrives = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM DiskDrives WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    Displays = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM Displays WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    EnvironmentVariables = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM EnvironmentVariables WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    HardwareDevices = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM HardwareDevices WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    HotFixes = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM HotFixes WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    LocalUsers = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM LocalUsers WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    LocalGroups = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM LocalGroups WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    LocalGroupMembers = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM LocalGroupMembers WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    MemoryModules = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM MemoryModules WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    NetworkAdapters = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM NetworkAdapters WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    LocalPrinters = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM Printers WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    ProductKeys = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM ProductKeys WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    Profiles = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM PowerShellScanner_4aa0b92dc0524234a90075fa296d3f84_View WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    Scanns = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM ScanProfileComputers WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    Services = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM Services WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    Shares = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM Shares WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    SharesPermission = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM SharePermissions WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    WindowsFeatures = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM WindowsFeatures WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    WindowsTaskSchedules = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM WindowsTaskSchedules WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    DesktopShortcuts = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM Files WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    Activation = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM PowerShellScanner_6162ffd7f94249d6a4964b5c08fad9b3_View WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    Bitlocker = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM PowerShellScanner_f609ba7492b54dbeaf1d7d72b05d50e4_View WHERE ComputerID LIKE '$($Computer.ComputerID)'"
    GPO = Invoke-SqliteQuery -DataSource $Database -Query "SELECT * FROM WMIScanner_31_View WHERE ComputerID LIKE '$($Computer.ComputerID)'"
}

I want to point out the Profiles object. This is the PowerShell scanner name for the profile size scanner. To grab the table names I suggest using the SQLite browser. You can download it here: Link.

Notice the bottom right hand side there is text. This text is the commands. You can browse around and find good information.

The call

Now you must create a function to call the script. This part is the easy part. We will be using the Invoke-command to trigger the script in question.

$Test = Invoke-Command -ComputerName $ServerName -ScriptBlock { c:\PSScripts\PDQ-Calls\Get-PDQComputerData.ps1 -ComputerName $args[0] } -ArgumentList $Computer

Now test will provide a large number of information for you. You can sort this information however you like. It will look something like this:

if ($Quick) {
        [PSCustomObject]@{
            Computername      = $test.computer.Name
            IPAddress         = $Test.Computer.IPAddress
            MacAddress        = $test.Computer.MacAddress
            CurrentUser       = $Test.Computer.CurrentUser
            BootTime          = $test.computer.BootTime
            Manufacturer      = $test.computer.Manufacturer
            Model             = $test.computer.model 
            SystemFamily      = $test.Computer.SystemFamily
            Chassis           = $test.Computer.Chassis
            SerialNumber      = $test.Computer.SerialNumber
            CPU               = $test.CPU.name
            Memory            = "$($Test.Computer.Memory /1gb)/gb"
            DiskDriveSize     = "$($Test.DiskDrives.Size /1gb)/gb"
            OSname            = $Test.Computer.OSName
            PhysicalDiskType  = $Test.DiskDrives.PhysicalDiskType
            bitlocker         = $test.bitlocker.ProtectionStatus
            WindowsActivation = $test.Activation.StatusReason
            WebrootVersion    = ($test.apps | where-object { $_.name -like "*webroot*" }).Version
            TeamViewerVersion = ($test.apps | where-object { $_.name -like "*TeamViewer*" }).Version
        }
    }

The above code is part of a script that will give me quick information. As you see, it should be treated as an XML because the data is loaded like an XML.

One of the things I have done is used this inside my universal dashboard to allow quick information that is formatted nicely.

Watch out

  • Here are some gotyas. If the database is busy, sqlite will break down on you.
  • If the disk is busy, the SQL lite will break down on you.
  • Don’t make to many requests as it slows everything down.