Call me...




Monday, October 25, 2021

A quick way to make a PoSh Array with a header

I am not really satisfied with how one is 'expected to create arrays in PoSh'...
The conventional methods look pretty, but I find them inflexible...
And I just don't like the way the result behaves... 

PSCustomObjects are a pain in the ass IMO... But I digress.

Here are some methods to look at, and an expanded example of how I use this approach to easily create multidimensional arrays:


$ThisX = @('one','two','nine') | Select @{n='Numbers';e={$_}}
$ThisX
$ThisX.Gettype() | Select IsPublic, IsSerializable, Name,BaseType | fl 

# ~ Result ~

# Numbers
# -------
# one    
# two    
# nine

# IsPublic       : True
# IsSerializable : True
# Name           : Object[]
# BaseType       : System.Array

#####################################

$ThisY = @('one') | Select @{n='Numbers';e={$_}}
$ThisY
$ThisY.Gettype() | Select IsPublic, IsSerializable, Name,BaseType | fl 

# ~ Result ~

# Numbers
# -------
# one

# IsPublic       : True
# IsSerializable : False
# Name           : PSCustomObject
# BaseType       : System.Object

####################################

$ThisZ = @('one')
$ThisZ
$ThisZ.Gettype() | Select IsPublic, IsSerializable, Name,BaseType | fl 

# ~ Result ~

# one

# IsPublic       : True
# IsSerializable : True
# Name           : Object[]
# BaseType       : System.Array

####################################

$This0 = "
zero,0
one,1
two,2
three,3
four,4
five,5
six,6
"
$This0 = ($This0).Split("`n|`r",[System.StringSplitOptions]::RemoveEmptyEntries)

$This0 = $This0 | Select @{n='Number';e={"$(($_).Split(',')[0])"}}, @{n='digit';e={[int]($_).Split(',')[1]}}, @{n='binary';e={[Convert]::ToString(([int]($_).Split(',')[1]),2)}}, Random

$This0.Gettype() | Select IsPublic, IsSerializable, Name,BaseType | fl 

$This0 

# ~ Result ~

# IsPublic       : True
# IsSerializable : True
# Name           : Object[]
# BaseType       : System.Array


# Number digit binary Random
# ------ ----- ------ ------
# zero       0 0            
# one        1 1            
# two        2 10           
# three      3 11           
# four       4 100          
# five       5 101          
# six        6 110  

################################

$This0 | % { $_.Random = 65..90|%{[char]$_}|Get-Random }


# Number digit binary Random
# ------ ----- ------ ------
# zero       0 0           E
# one        1 1           N
# two        2 10          I
# three      3 11          L
# four       4 100         Y
# five       5 101         T
# six        6 110         Z

Tuesday, July 20, 2021

Delinquent KnowBe4 training list - API seriously / sadly lacking

If any of you folks use KnowBe4...
The below script may be helpful...
I tapped into their API - Hoping to pull in an automated list of users who have not completed mandatory training -

This list of users was being manually harvested, via the web interface (not an easy, intuitive, or remotely contiguous process either).

Only to discover that such base level, rudimentary details are not actually available from a simple API call...
I even asked their support team... Nope!


To say the least, I was dismayed, and annoyed in learning this.


(I did promise KnowBe4 support that I would call them out on Reddit, hoping that some amount of public embarrassment, might get their dev team to fix " this shortsighted absurdity"... " And act like a company that offers IT related services" - 
https://www.reddit.com/r/PowerShell/comments/oo9ir1/delinquent_knowbe4_training_list_api_seriously/

https://www.reddit.com/r/sysadmin/comments/oo9n51/delinquent_knowbe4_training_list_api_seriously/

I sent a link to those Reddit posts, in my reply to the KnowBe4 manager that replied to me, when I explained my feelings about this, in the support email)

~~~~~~~~~~~~~~~~~~~~~~~~~~
It is necessary to call three (3) different API directories, in order to get something useful.
Querying first ‘Campaigns’, and then using THAT result, and querying ‘Enrollments’, and THEN using THAT result, querying ‘users’.

The below code does a far bit of data massaging, and may be interspersed with some filtering that not everyone might need in place...
No doubt there are ways to streamline it - TBH - I don't really care.

Much of what I did, was done on-the-fly... And like I said - I was annoyed... I probably still am.


$EAP = $ErrorActionPreference
$ErrorActionPreference = "SilentlyContinue" # Because possibly dealing with converting a null value to 'DateTime', below.

Function Token {
$AccessToken = "'Reporting' API key goes in here" # Get this from within the KnowBe4 portal
$authorizationHeader = "Authorization: Bearer $AccessToken"   
}

$URL = 'https://us.api.knowbe4.com/v1/training/campaigns/'
Token
$campaigns = $null; $campaigns = (curl.exe --url $URL --header $authorizationHeader --silent | ConvertFrom-Json)
# $campaigns | select name, campaign_id, status, completion_percentage, start_date, end_date, relative_duration, duration_type | sort completion_percentage | ft

$campaigns | % {
If ($_.start_date -ne $null) { $_.start_date = [DateTime]"$($_.start_date)" }
If ($_.start_date -eq $null) { $_.start_date = [Nullable[DateTime]]$null }
If ($_.end_date -ne $null) { $_.end_date = [DateTime]"$($_.end_date)" }
If ($_.end_date -eq $null) { $_.end_date = [Nullable[DateTime]]$null }
}

$PastDue = @()
$campaigns | ? { $_.Status -ne "closed" -and ($_.end_date -eq $null -or (Get-Date) -lt $_.end_date) } | sort name  | % {

    Write-Host $_.campaign_id -Fore 14 -No
    Write-Host " - "$_.name -Fore 11 -No
    If ($_.relative_duration) { 
        $_relative_duration = $_.relative_duration
        Write-Host " - Rel. Dur.:"$_.relative_duration -No 
    }
    If (!$_.relative_duration) { 
    
    }
    If ($_.end_date -ne $null) { 
        Write-Host " (End date: $($_.end_date))" -Fore 10 
    }
    If ($_.end_date -eq $null) { 
        Write-Host " (No End date)" 
    }
#>
$URL = "https://us.api.knowbe4.com/v1/training/enrollments?campaign_id=$($_.campaign_id)?per_page=500"
Token
$enrollments = $null; $enrollments = (curl.exe --url $URL --header $authorizationHeader --silent | ConvertFrom-Json)
$enrollments = $enrollments | select campaign_name, completion_date, content_type, enrollment_date, enrollment_id, module_name, policy_acknowledged, start_date, status, user_status, user, id, first_name, last_name, email
$enrollments | % {
$_.id = $_.user.id
$_.first_name = $_.user.first_name
$_.last_name = $_.user.last_name
$_.email = $_.user.email
}

$PastDue += $enrollments | ? { $_.status -eq "past due" } | select campaign_name, enrollment_date, @{ Name='duration';Expression ={ $_relative_duration } }, days_late, module_name, status, user_status, id, first_name, last_name, email } 
$ErrorActionPreference = $EAP

$PastDue | sort campaign_name, email  | % {
$duration_period = (($_.duration).Trim()).Split(' ')
If ($duration_period[1] -match "weeks")  { $_days = ((Get-Date).AddDays(-([int]$duration_period[0] *7)) - [Datetime]"$($_.enrollment_date)").Days } 
If ($duration_period[1] -match "months") { $_days = ((Get-Date).AddMonths(-([int]$duration_period[0])) - [Datetime]"$($_.enrollment_date)").Days }
$_.days_late = "  $_days "
$_.enrollment_date = [DateTime]"$($_.enrollment_date)"
}
# Use the user 'Active', or 'Archived'?
$PastDue | % { $StatusURL = "https://us.api.knowbe4.com/v1/users/$($_.id)"; Token; $_.user_status = (curl.exe --url $StatusURL --header $authorizationHeader --silent | ConvertFrom-Json).Status }

''; $PastDue | ? {$_.user_status -eq "Active" -and $_.campaign_name -NOTmatch "The.Inside.Man"} | sort campaign_name, email | select campaign_name,duration,days_late,status,user_status,id,first_name,last_name,email | ft

Thursday, July 1, 2021

User info Function

I call this function into both Console, and ISE profiles.
It will pull in the contents of the clipboard, and pull in pertinent user info.
For me - The users location is really helpful, but It will also show if a users account is locked, the password, or the account is expired, etc...

If the clipboard contents don't work, it will rerun the function and prompt...



Function Office {

$EAP = $ErrorActionPreference 
$ErrorActionPreference = "SilentlyContinue"
$Who = Get-Clipboard

if ($Who -eq "x") {Write-Host " - Exiting..."; break}
If ($Who -is [array]) {
$Who =  [string]::Join(" ",$Who)
}
If (!$Who -or $Who.Length -gt 30) {
Write-Host "Full name, username, or email address? " -NoNewline -ForegroundColor Yellow -BackgroundColor DarkMagenta
Write-Host "('X' to exit)" -NoNewline -ForegroundColor Cyan -BackgroundColor DarkMagenta
Write-Host ":" -NoNewline -ForegroundColor Yellow -BackgroundColor DarkMagenta; Write-Host " " -NoNewline
$Who = (Read-Host).Trim()
}
If ($Who -eq "x") {break}
If ($Who) {$Who = "$(($Who).Trim())"}

If (!$Who) {Write-host "Nothing to look for / Nothing entered." -ForegroundColor Yellow -BackgroundColor Black; break}
$Quality = 0
If ($Who -NOTmatch " " -and $Who -NOTmatch "@") { $Quality = 1; Write-Host "Looking for this username: $Who" -ForegroundColor Cyan }
If ($Who -match "@") { $Quality = 2; Write-Host "Looking for this email address: $Who" -ForegroundColor Cyan }
If ($Who -match " ") { $Quality = 3; Write-Host "Looking for this name: $Who" -ForegroundColor Cyan }

Function ADU1 {
$ErrorActionPreference = "SilentlyContinue"
Get-ADUser -filter * -Properties City, State, 
telephoneNumber, mail, Title, Manager, Description, 
PasswordLastSet, LastLogonDate, PasswordExpired, PasswordNeverExpires, 
lockoutTime, LockedOut, AccountExpirationDate
$ErrorActionPreference = "Continue"
} 
$Output = $null
If ($Quality -eq 1) { $Output = ADU1 | ? {$_.SamAccountName -match $Who} }
If ($Quality -eq 2) { $Output = ADU1 | ? {$_.mail -match $Who} }
If ($Quality -eq 3) { $Output = ADU1 | ? {$_.Name -match $Who} }

If (!$Output) {Write-host "`'$Who`' is not valid." -ForegroundColor Yellow -BackgroundColor Black
Set-Clipboard $null
office
}

$Pwd_Age = "$( ( (Get-Date)-($Output.PasswordLastSet) ).Days ) day(s), $(((Get-Date)-($Output.PasswordLastSet) ).Hours) hour(s), $(((Get-Date)-($Output.PasswordLastSet) ).Minutes) minute(s) ago"

$lockoutTime = $Output.lockoutTime
If ($lockoutTime -gt 0) {$lockoutTime = [datetime]::FromFileTime($($lockoutTime))}

$Output = $Output | Select Name, `
@{Name='Username';Expression ={($_.SamAccountName).ToUpper()}}, `
@{Name='Desc';Expression ={(($_.Description).Replace('|','-'))}},`
@{Name='Location';expression={"$($Output.City), $($Output.State)"}},` `
@{Name='Phone';Expression={$("{0:# ###-###-####}" -f [int64](($_.telephoneNumber).Trim('+')))}}, `
Mail, Title, `
@{Name='Manager';Expression ={"$((Get-ADUser ((get-aduser ($_.SamAccountName) -Properties Manager).Manager) | Select `
    @{name="Manager";expression={"$($_.Name) ($(($_.SamAccountName).ToUpper())) - $($_.UserPrincipalName)"}}).Manager)"}}, `
@{name="Last Logon";expression={$_.LastLogonDate}},`
@{name="Lockout time";expression={$lockoutTime}},` 
@{name="Locked out";expression={$_.LockedOut}},` 
@{name="Pwd No Exp";expression={$_.PasswordNeverExpires}},`
@{name="Expiration";expression={$_.AccountExpirationDate}},` 
@{name="Pwd Last Set";expression={$_.PasswordLastSet}},` 
@{name="Pwd Age";expression={$Pwd_Age}},` 
@{name="Pwd Expired";expression={$_.PasswordExpired}}

Write-Host " "

($Output | Out-String).Split("`n|`r",[System.StringSplitOptions]::RemoveEmptyEntries)  | % {
If ($_ -like "Name *") {Write-Host "   $_ ($($Output.Username))"}
If ($_ -like "Location *" ) { Write-Host "     $(($_).Replace('  :',':'))" -ForegroundColor Green }
If ($_ -like "Desc *" -and $Output.Desc -ne $null) {Write-Host "   $_"}
If ($_ -like "Phone *" -and $Output.Phone -ne $null) {Write-Host "   $_"}
If ($_ -like "Mail *") {Write-Host "   $_"}
If ($_ -like "Title *") {Write-Host "   $_"}
If ($_ -like "Manager *") {Write-Host "   $_"}
If ($_ -like "Last Logon *") {Write-Host "   $_"}
If ($_ -like "Expiration *" -and $Output.Expiration -ne $null) { Write-Host "   " -No; Write-Host "$_"  -Fore Red -Back Yellow }
If ($_ -like "Locked out *" -and $Output."Lockout time" -gt 0) {Write-Host "   $_"}
If ($_ -like "Last Logon *" -and $Output."Lockout time" -gt 0) {Write-Host "   $_"}
If ($_ -like "Pwd Last Set *" -and $Output."Pwd No Exp" -ne $True) {Write-Host "   $_" -No}
If ($_ -like "Pwd Expired *" -and $Output."Pwd Expired" -eq $True) {Write-Host " <EXPIRED> " -Fore Red -Back Yellow -No}
If ($_ -like "Pwd Age *" -and $Output."Pwd No Exp" -ne $True) {Write-Host " [$($Output.'Pwd Age')]" -Fore Yellow}
}

If ($host.name -eq "ConsoleHost") {''}
Set-Clipboard $null
$ErrorActionPreference = $EAP 

} # END Function Office {

set-alias -name ofc -value Office

Tuesday, June 22, 2021

PowerShell Single Column to Multiple Columns

This thing will take a single column of text, split it up into columns and put it on screen - But it could be adapted easily to some other output.
A couple of things to note:

  • '$pad' is there for readability - so the columns have some padding.
  • '-HideTableHeaders'
  • The 'Function' items inside this, especially the 'ListToArray' comes in handy for me all the time. I dropped it in this way, just to share it in a way that that stood out.
  • This could be done in fewer lines, and I do like brevity, but I wanted to leave it more 'spelled' out, for illustrative purposes.

Using the 50 states to show it working.
Function '#of columns' "Data source"

Column2Columns 3 $Data
Column2Columns 5 $Data
Column2Columns 10 $Data

Examples below the code:

$Data = ("Alabama
Alaska
Arizona
Arkansas
California
Colorado
Connecticut
Delaware
Florida
Georgia
Hawaii
Idaho
Illinois
Indiana
Iowa
Kansas
Kentucky
Louisiana
Maine
Maryland
Massachusetts
Michigan
Minnesota
Mississippi
Missouri
Montana
Nebraska
Nevada
New Hampshire
New Jersey
New Mexico
New York
North Carolina
North Dakota
Ohio
Oklahoma
Oregon
Pennsylvania
Rhode Island
South Carolina
South Dakota
Tennessee
Texas
Utah
Vermont
Virginia
Washington
West Virginia
Wisconsin
Wyoming")

Function
Column2Columns {
################################
<#
.DESCRIPTION
      Takes a single column of data (as a string), splits it into a specified number of columns, as its output.

.EXAMPLE
    # Three columns
      Column2Columns 3 $Data

.EXAMPLE
    # Two columns
      Column2Columns 2 $Blah

.EXAMPLE
    # Four columns
    Column2Columns 4 $LongList
#>
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [int]  $Columns,
         [Parameter(Mandatory=$true, Position=1)]
         [string] $Data
    )

Function
ListToArray { ($Args).Split("`n|`r",[System.StringSplitOptions]::RemoveEmptyEntries) }
Function AddMembr {$Stack | Add-Member -membertype noteproperty -name "Item$Counter_Data" -Value "$_$pad"}
$pad = "    "
$Counter_Data = 0
$Columns_Array = @()
$Stack = new-object psobject

ListToArray
$Data |  % {
$Counter_Data++
  If ($Counter_Data -lt ($Columns)) { AddMembr }
  If ($Counter_Data -ge ($Columns)) { AddMembr
    $Counter_Data = 0
    $Columns_Array += $Stack
    $Stack = new-object psobject
  }
}

$Columns_Array
+= $Stack
$Columns_Array =  $Columns_Array | ? { !([string]::IsNullOrEmpty($_)) }
$Columns_Array | ft -HideTableHeaders
}
# END Function

 

Examples:



Wednesday, June 16, 2021

'ipconfig /all' - Posh version

I wanted a more comprehensive, and easier to read, output of a systems network settings, etc...

I have this set as a Function and it has an alias.

I call theses functions from both PoSh profiles.

This is what the output looks like:

The script:

Function Get-IP {

Write-Host "Fetching IP info..." -ForegroundColor Gray -BackgroundColor Black
$AdapterList = @()
Get-NetAdapter | ? {$_.InterfaceDescription -NOTmatch 'Bluetooth'| % {
$adapter = $_
$prefix = (Get-NetIPAddress -InterfaceIndex $adapter.InterfaceIndex | ? {$_.ipv4address}).prefixlength
('1' * $prefix).PadRight(32, '0') | Out-Null;$bitString=('1' * $prefix).PadRight(32,'0'); $SnM=[String]::Empty
    for($i=0;$i -lt 32;$i+=8)
    {$byteString=$bitString.Substring($i,8); $SnM+="$([Convert]::ToInt32($byteString, 2))."}
If ((Get-NetIPInterface -InterfaceAlias $_.InterfaceAlias).Dhcp -eq "Enabled") {$DHCP = "DHCP"} else {$DHCP = "STATIC"}
$AdapterList += ((Get-NetIPConfiguration | ? {$_.InterfaceIndex -match "$($adapter.InterfaceIndex)"} | select
    @{Name='Alias';Expression ={$_.InterfaceAlias}},`
    @{Name='Status';Expression ={$_.NetAdapter.Status}},`
    @{Name='IPAddress';Expression ={"$($_.IPv4Address.IPAddress) ($DHCP)"}},`
    @{Name='SubnetMask';Expression ={"$($SnM.TrimEnd('.')) (/$prefix)"}},`
    @{Name='Gateway';Expression ={$_.IPv4DefaultGateway.NextHop}},`
    @{Name='DNSServer(s)';Expression ={$_.DNSServer.ServerAddresses}},`
    @{Name='MAC';Expression ={$adapter.MacAddress}},`
    @{Name='Index';Expression ={$_.InterfaceIndex}},`
    @{Name='Description';Expression ={$_.InterfaceDescription}}))
    }
Write-Host "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" -ForegroundColor Gray
$AdapterList | % {
$_ | % {
If ($_.IPAddress -match "169.") {
    $_.IPAddress = "($DHCP)"
    $_.SubnetMask = " - "
}

$Item00
= $_
($Item00 | Out-String).Split("`n|`r",[System.StringSplitOptions]::RemoveEmptyEntries) | % {
If ($Item00.Status -match "Disconnected") {Write-Host "█ " -fore Red -No }
If ($Item00.Status -match "Up") {Write-Host "¤ " -fore Green -No }
If ($_ -match "Index") {Write-Host " " $_ -ForegroundColor cyan}
If ($_ -match "Alias") {Write-Host " " $_ -ForegroundColor Yellow}
If ($_ -match ": Up") {Write-Host " " $_ -ForegroundColor Green}
If ($_ -match ": Disconnected") {Write-Host " " $_ -ForegroundColor Magenta}
If ($_ -NOTmatch "Index" -and $_ -NOTmatch "Alias" -and $_ -NOTmatch "Status") {Write-Host " " $_}
}

(
$Item00 | Out-String).Split("`n|`r",[System.StringSplitOptions]::RemoveEmptyEntries) | % {
    If ($_ -match "Alias" -and $_ -match "Wi-Fi" -and $Item00.Status -match "Up") {
        netsh wlan show interfaces | % {
            If ($_ -match "SSID" -and $_ -NOTmatch "BSSID") {$SSID = $_}
            If ($_ -match "State") {$State = $_}
            If ($_ -match "Signal") {$Signal = $_}
            If ($_ -match "Authentication") {$Auth = $_}
        }
            Function SplitWrite($arg0) {Write-Host ($arg0.Split(':')[1]).Trim() -Fore 11}
                Write-Host "             SSID: " -Fore 14 -No; SplitWrite $SSID
                Write-Host "            State: " -Fore 14 -No; SplitWrite $State
                Write-Host "           Signal: " -Fore 14 -No; SplitWrite $Signal
                Write-Host "             Auth: " -Fore 14 -No; SplitWrite $Auth
    }
}

}
Write-Host "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" -ForegroundColor Gray
}

}
# End Function Get-IP

set-alias
-name gip -value Get-IP


On GitHub: 
Get-IP.psm1