Local Website

Local Website

Here we are again, trying to make a device work from one center to another. Each center has the same server IP address but different subnets. All the subnets are /24. So, that makes life easier. All of them are .5 as well. So, that makes life much easier. So, how do we get a website started on .5 from every device but able to take it to another site? Well, the solution is simple. You find the IP address and tell the internet browser to start it for you.

First lets get the IP address and sort through it.

for /f "usebackq tokens=2 delims=:" %%f in (`ipconfig ^| findstr /c:"IPv4 Address"`) do set "ip=%%f"

The above is a loop for IP config to remove anything that is ipv6 to IPv four. Once we have our IPv4 Address we then loop through that and set each octet that we want to use.

for /f "tokens=1-4 delims=. " %%a in ("%ip%") do (
set octetA=%%a
set octetB=%%b
set octetC=%%c
set octetD=%%d
)

We sort through the IPv4 address and split each item via the . delimiter. Now we take that info and start our IE.

start "CMD" /D "C:\Windows\System32\" /max "C:\Program Files\Internet Explorer\iexplore.exe" "http://%octetA%.%octetB%.%octetC%.5/LocalWebsite/"

That’s it, a very simple process, but it can help with so much time.

The Script

@echo off
rem All i need to do now is grab only the IP address with batch. 
rem ipconfig | find "IPv4"
for /f "usebackq tokens=2 delims=:" %%f in (`ipconfig ^| findstr /c:"IPv4 Address"`) do set "ip=%%f"

for /f "tokens=1-4 delims=. " %%a in ("%ip%") do (
set octetA=%%a
set octetB=%%b
set octetC=%%c
set octetD=%%d
)

start "CMD" /D "C:\Windows\System32\" /max "C:\Program Files\Internet Explorer\iexplore.exe" "http://%octetA%.%octetB%.%octetC%.5/LocalWebsite/"
exit

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.

2FA and Outlook

2FA and Outlook

A friend’s company turned on 2fa for their office 365. When people logged into office.com they were prompted to 2-factor authenticate with the system. Some choose to call in, some choose text, and others choose the app. It was doing good, but outlook gave them troubles. It turns out that office 2013 and office 2016 installs struggle with 2fa. However, there is salvation! A registry edit.

for office 2013:

HKCU\SOFTWARE\Microsoft\Office\15.0\Common\Identity\EnableADAL

for Office 2016

HKCU\SOFTWARE\Microsoft\Office\16.0\Common\Identity\EnableADAL

Set this to a REG_DWORD of 1.

For more information, you can read all about it from the Microsoft documentation page:

https://docs.microsoft.com/en-us/microsoft-365/enterprise/modern-auth-for-office-2013-and-2016?view=o365-worldwide

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!