Use PowerShell to Set Microsoft SQL Database Owner

$owner='CAP\SQL Admins'
$databaseName='TestDb'
$sqlServer=$env:computername

function setDbOwner{
  param(
    $principle=$env:USERDOMAIN+'\Domain Admins',
    $databaseName='TestDB',
    $sqlServer
  )
  
  function includeSqlTools{
    $ErrorActionPreference='stop'
    try{
      $trustedPsgallery=(Get-PSRepository PSGallery).InstallationPolicy -eq 'Trusted'
      if(!$trustedPsgallery){
          Set-PSRepository -Name PSGallery -InstallationPolicy Trusted 
      }
      if(!(Get-Module sqlserver)){
          Install-Module sqlserver -Confirm:$False
      }
      if(!(Get-Module dbatools)){
          Install-Module dbatools -Confirm:$False
      }
      Import-Module sqlserver  
      Import-Module dbatools
      return $true
    }catch{
      write-warning $_
      return $false
    }
  }

  try{
    if(!(includeSqlTools)){
      write-warning "Cannot proceed with SQL Tools"
      return $false
    }
    $server=New-Object ('Microsoft.SqlServer.Management.Smo.Server') $sqlServer
    $db=New-Object Microsoft.SqlServer.Management.Smo.Database
    $db=$server.Databases.Item($databaseName)
    $db.SetOwner($principle, $TRUE)
    $db.Alter()
    return $true
  }catch{
    Write-Warning
    return $false
  }
}

setDbOwner $owner $databaseName $sqlServer

Use PowerShell to Grant SysAdmin Role to Certain Users

$principle=$env:USERDOMAIN+'\Domain Admins'
$sqlServer=$env:computername

function includeSqlTools{
  $ErrorActionPreference='stop'
  try{
    $trustedPsgallery=(Get-PSRepository PSGallery).InstallationPolicy -eq 'Trusted'
    if(!$trustedPsgallery){
        Set-PSRepository -Name PSGallery -InstallationPolicy Trusted 
    }
    if(!(Get-Module sqlserver)){
        Install-Module sqlserver -Confirm:$False
    }
    if(!(Get-Module dbatools)){
        Install-Module dbatools -Confirm:$False
    }
    Import-Module sqlserver  
    Import-Module dbatools
    return $true
  }catch{
    write-warning $_
    return $false
  }
}

function grantSysadmin($principle,$sqlServer=$env:computername){
    if(!(includeSqlTools)){
        write-warning "Unable to proceed without SQL Tools"
        return $false
    }
    $sqlPortOpen=test-netconnection $sqlServer -port 1433 -informationlevel quiet
    $result=.{if($sqlPortOpen){
      try{
        $server=New-Object ('Microsoft.SqlServer.Management.Smo.Server') $sqlServer
        $sysadmins=$server.Roles|?{$_.Name -eq 'sysadmin'}
        $sysadmins.AddMember($principle)
        write-host "Current sysadmin principles:`r`n$(($sysadmins.EnumMemberNames()|out-string).trim())"
        return $true
      }catch{
        Write-Warning
        return $false
      }
    }else{
      # Workaround for firewall issues blocking SQL port between jump host and SQL Server
      invoke-command -computername $sqlServer {
        param($principle,$includeSqlTools)
        $sqlTools=[scriptblock]::create($sqlTools).invoke()
        if(!$sqlTools){
          write-warning "Unable to proceed without SQL Tools"
          return $false
        }
        try{
          $server=New-Object Microsoft.SqlServer.Management.Smo.Server("(localhost)")
          $sysadmins=$server.Roles|?{$_.Name -eq 'sysadmin'}
          $sysadmins.AddMember($principle)
          write-host "Current sysadmin principles:`r`n$(($sysadmins.EnumMemberNames()|out-string).trim())"
          return $true
        }catch{
          write-warning $_
          return $false
        }
      } -Args $principle,${function:includeSqlTools}
    }
  }
  return $result   
}

grantSysadmin $principle $sqlServer

PowerShell: Auto Login to WordPress and Update Gold Prices WooCommerce Plugin

# API URI - these are examples, only
$goldPriceApi='https://dragoncoin.com/XAU/USD'
$silverPriceApi='https://dragoncoin.com/XAG/USD'
$platinumApi='https://dragoncoin.com/XPT/USD'
$palladiumApi='https://dragoncoin.com/XPD/USD'

# WordPress WooCommerce Gold Price Plugin
$username='someadminaccount'
$password='somepassword'
$goldPriceFieldId='woocommerce_gold_price_options_24'
$agFieldId='woocommerce_gold_price_options_22'
$ptFieldId='woocommerce_gold_price_options_18'
$pdFieldId='woocommerce_gold_price_options_14'
$submitButtonClass='button-primary'
$expectedPageTitle='Gold Prices and Gold Products*'
$goldPricePage="https://dragoncoin.com/wp-admin/admin.php?page=woocommerce_gold_price"
$updateSeconds=120

# Other variables
$countDown=0
$timesToDeterminePause=5
$pauseSeconds=3600

function autologinSe{
    param(
        $url,
        $username,
        $password,
        $usernameElementId='user_login',
        $passwordElementId='user_pass',
        $submitButtonId='wp-submit',
        $exitIeWhenDone=$false
    )
    $ErrorActionPreference = 'continue'

    # Initial validation
    if(!$url){write-warning "No URL specified.";return $false}

    function killInternetExplorer{
        $ieInstances=(New-Object -COM 'Shell.Application').Windows()|?{$_.Name -like '*Internet Explorer*'} 
        $ieInstances|%{$_.Quit()
        [Runtime.Interopservices.Marshal]::ReleaseComObject($_)
        }
        [GC]::Collect()
        [GC]::WaitForPendingFinalizers()
        }

    function enableIeProtectedMode{
        # $hives = 0..4|%{"HKLM:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\$_"}
        $hives = 0..4|%{"HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\$_"}
        $keyName='2500' # Key Name '2500' corresponds to 'Protected Mode' in IE
        
        #Skipping zone 0 as that is the default local machine zone
        $hives[1..4]|%{Set-ItemProperty -Path $_ -Name $keyName -Value 0}
        $keys=$hives|%{Get-ItemProperty -Path $_}|select DisplayName, `
                                                        @{name='status';e={
                                                                        if($_.$keyName -eq 0){'enabled'}
                                                                        elseif($_.$keyName -eq 3){'disabled'}
                                                                        else{'n/a'}                                                                                        
                                                                        }}
        write-host "IE Protected Mode Standardized Values:`r`n$($keys|out-string)" 
    }

    function disableIeProtectedMode{
        # $hives = 0..4|%{"HKLM:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\$_"}
        $hives = 0..4|%{"HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\$_"}
        $keyName='2500' # Key Name '2500' corresponds to 'Protected Mode' in IE
        
        #Skipping zone 0 as that is the default local machine zone
        $hives[1..4]|%{Set-ItemProperty -Path $_ -Name $keyName -Value 3}
        $keys=$hives|%{Get-ItemProperty -Path $_}|select DisplayName, `
                                                        @{name='status';e={
                                                                        if($_.$keyName -eq 0){'enabled'}
                                                                        elseif($_.$keyName -eq 3){'disabled'}
                                                                        else{'n/a'}                                                                                        
                                                                        }}
        write-host "IE Protected Mode Standardized Values:`r`n$($keys|out-string)" 
    }

    function allowActiveX($zone='Trusted'){
        #Source: http://support.microsoft.com/KB/182569
        $zoneCode=switch($zone){
            'My Computer'{0;break}
            'Local Intranet'{1;break}
            'Trusted'{2;break}
            'Internet'{3;break}
            'Restricted Sites'{4;break}
            default{2}
            }
        #Reference table:
        #Value    Setting
        #------------------------------
        #0        My Computer
        #1        Local Intranet Zone
        #2        Trusted sites Zone
        #3        Internet Zone
        #4        Restricted Sites Zone
        $hashMap=@{
            '2702'=0 #ActiveX controls and plug-ins: Allow ActiveX Filtering = Enable (2702)
            '1208'=0 #ActiveX controls and plug-ins: Allow previously unused ActiveX controls to run without prompt = Enable (1208)
            '1209'=0 #ActiveX controls and plug-ins: Allow Scriptlets = Enable (1209)
            '2201'=3 #ActiveX controls and plug-ins: Automatic prompting for ActiveX controls = Disable (2201)
            '2000'=0 #ActiveX controls and plug-ins: Binary and script behaviors = Enable (2000)
            '120A'=0 #Display video and animation on a webpage that does not use external media player = Enable (120A)
            '1001'=0 #ActiveX controls and plug-ins: Download signed ActiveX controls = Enable (1001)
            '1004'=0 #ActiveX controls and plug-ins: Download unsigned ActiveX controls = Enable (1004)
            '1201'=0 #ActiveX controls and plug-ins: Initialize and script ActiveX controls not marked as safe for scripting = Enable (1201)
            '120B'=3 #Only allow approved domains to use ActiveX without prompt = Disable (120B)
            '1200'=0 #ActiveX controls and plug-ins: Run ActiveX controls and plug-ins = Enable (1200)
            '1405'=0 #ActiveX controls and plug-ins: Script ActiveX controls marked as safe for scripting = Enable (1405)
            }        
        
        $trustedDomains="HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\zones\$zoneCode"
        $currentValues=Get-ItemProperty $trustedDomains
        foreach ($item in $hashMap.GetEnumerator()) {
            $key = $item.Key
            $value = $item.Value
            if($currentValues.$key -ne $value){
                    New-ItemProperty -Path $trustedDomains -Name $key -Value $value -PropertyType DWORD -Force
                }
        }
    }

    function addDomainToTrustedSites($url){
        $httpType=.{[void]($url -match '^(https{0,1})');$matches[1]}
        $domain=([uri]$url).Host
        #$rootDomain=$domain.split('.')[-2..-1] -join '.' # This is assuming that the TLD is one-dotted (e.g. .com) not two-dotted (e.g. co.uk)
        $rootDomain=.{$fragments=$domain.split('.')
                    $fragments[1..$($fragments.count)] -join '.'
                    }
        write-host "Root domain detected`t: $rootDomain"        
        # The more advanced function to retrieve this value is at https://kimconnect.com/powershell-extract-root-domain-from-url
        if ($rootDomain -notmatch '\.' -or $rootDomain -eq $env:USERDNSDOMAIN){
            write-host "There's no need to add $url to the Trusted zone as it is local to this domain."
            return $true
            }
        $dwordValue=2 # value of true correlates to 'enable'
        $domainRegistryPath='HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains'
        $domainRegistryPath2='HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\EscDomains' #EscDomains key applies to those protocols that are affected by the Enhanced Security Configuration (ESC)
        $null=New-Item -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap' -ItemType File -Name 'EscDomains' -Force
        $null=New-Item -Path "$domainRegistryPath" -ItemType File -Name "$rootDomain" -Force
        $null=New-Item -Path "$domainRegistryPath2" -ItemType File -Name "$rootDomain" -Force
        $null=Set-ItemProperty -Path "$domainRegistryPath\$rootDomain" -Name $httpType -Value $dwordValue
        $null=Set-ItemProperty -Path "$domainRegistryPath2\$rootDomain" -Name $httpType -Value $dwordValue

        # Also add {about:blank} record as that doesn't seem to have been added by default
        if (!(test-path "$domainRegistryPath\blank")){
            #New-ItemProperty -Path $trustedDomains -Name $key -Value $value -PropertyType DWORD -Force
            $null=New-Item -Path "$domainRegistryPath" -ItemType File -Name 'blank'
            $null=Set-ItemProperty -Path "$domainRegistryPath\blank" -Name 'about' -Value $dwordValue
            }
        if (!(test-path "$domainRegistryPath2\blank")){
            $null=New-Item -Path "$domainRegistryPath2" -ItemType File -Name 'blank'
            $null=Set-ItemProperty -Path "$domainRegistryPath2\blank" -Name 'about' -Value $dwordValue
            }                     

        # Also add {about:internet} record since it will stop a login when missing
        if (!(test-path "$domainRegistryPath\internet")){
            $null=New-Item -Path "$domainRegistryPath" -ItemType File -Name 'internet'
            $null=Set-ItemProperty -Path "$domainRegistryPath\internet" -Name 'about' -Value $dwordValue
            }
        if (!(test-path "$domainRegistryPath2\internet")){
            $null=New-Item -Path "$domainRegistryPath2" -ItemType File -Name 'internet'
            $null=Set-ItemProperty -Path "$domainRegistryPath2\internet" -Name 'about' -Value $dwordValue
            } 
            
        $valueAfterChanged=(Get-ItemProperty "$domainRegistryPath\$rootDomain")."$httpType"
        $value2AfterChanged=(Get-ItemProperty "$domainRegistryPath2\$rootDomain")."$httpType"
        if ($valueAfterChanged -eq 2 -and $value2AfterChanged -eq 2 ){
            write-host "$rootDomain has been added to Internet Explorer"
            return $true
            }
        else{
            write-warning "$rootDomain has NOT been added to Internet Explorer."
            return $false
            }
    }

    function includeSelenium{
        Import-Module Selenium -ea SilentlyContinue
        if (!(get-module selenium -EA SilentlyContinue)){
            Start-job {
                [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                if(!(Get-PackageProvider Nuget -ea SilentlyContinue)){Install-PackageProvider -Name NuGet -Force}
                # Defining $ENV:ChocotaleyInstall so that it would be called by refreshenv
                $ENV:ChocolateyInstall = Convert-Path "$((Get-Command choco).Path)\..\.."   
                Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
                Install-Module Selenium -Force -Confirm:$False
                } |Receive-Job -Wait
            Update-SessionEnvironment
            Import-Module Selenium
            }
        }

    function invokeSelenium($url,$userName,$password,$usernameElementId,$passwordElementId,$submitButtonId){
        $ErrorActionPreference = "Stop"
        function closeSelenium($selenium){
            if($selenium){
                $selenium.close()
                $selenium.quit()
                }
            }
        
        function noLogin($url){
            $seleniumIe = New-Object "OpenQA.Selenium.IE.InternetExplorerDriver"
            $seleniumIe.Navigate().GoToURL($url)
            $title=$seleniumIe.Title
            write-host "Page reached: '$title'"
            $trustedSiteError=$title -match '^Error'
            if($trustedSiteError){
                write-host "An site trust issue has been detected. Adding root domain to the trusted sites list to resolve this issue."
                addDomainToTrustedSites $url              
                closeSelenium $seleniumIe                
                return $false
                }
            else{
                return $seleniumIe                
                }
            }

        function login($url,$username,$password,$usernameElementId,$passwordElementId,$submitButtonId){
            $seleniumIe = New-Object "OpenQA.Selenium.IE.InternetExplorerDriver"
            $seleniumIe.Navigate().GoToURL($url)
            $userField=$seleniumIe.FindElementById($usernameElementId)
            $userField.clear()
            $userField.SendKeys($username)
            $passwordField=$seleniumIe.FindElementById($passwordElementId)
            $passwordField.SendKeys('')
            $passwordField.clear()           
            $passwordField.SendKeys($password)
            $submitButton=$seleniumIe.FindElementById($submitButtonId)
            $submitButton.Click()
            $title=$seleniumIe.Title
            write-host "Page reached: '$title'"
            $trustedSiteError=$title -match '^Error'
            if($trustedSiteError){
                write-warning "A site trust issue has been detected."                
                closeSelenium $seleniumIe                
                return $false
            }else{
                return $seleniumIe                
                }
            }
        
        try{
            $null=allowActiveX
            $isLogin=$userName,$password,$usernameElementId,$passwordElementId,$submitButtonId|?{!(!$_)}
            if($isLogin){
                write-host "Login to $url as $userName..."
                $ie=login $url $userName $password $usernameElementId $passwordElementId $submitButtonId
            }else{
                write-host "Accesing $url without login..."
                $ie=nologin $url
                }
            return $ie
            }
        catch{            
            Write-Warning $Error[0].Exception.Message
            return $false
            }
        }

    try{
        write-host "Username`t: $username`r`nPassword`t: $(!(!$password))`r`nusernameElementId`t: $usernameElementId`r`npasswordElementId`t: $passwordElementId`r`nsubmitButtonId`t: $submitButtonId"
        $null=includeSelenium
        $null=disableIeProtectedMode
        $null=addDomainToTrustedSites $url                   
        if(get-module selenium -ea SilentlyContinue){
            $isLogin=$userName,$password,$usernameElementId,$passwordElementId,$submitButtonId|?{!(!$_)}
            if($isLogin){                
                $selenium=invokeSelenium $url $userName $password $usernameElementId $passwordElementId $submitButtonId
            }else{
                write-host "No username or password are given. Proceeding to access only the provided URL."
                $selenium=invokeSelenium $url
                }
        }else{
            write-warning "Please manually verify that the Selenium module is installed before retrying this function."
            }
        if($selenium){            
            if($exitIeWhenDone){
                $null=killInternetExplorer
                return $true
            }else{
                return $selenium
                }
        }else{
            write-warning "There were errors preventing a successful login."
            return $false
            }
        }
    catch {
        write-warning "$_"        
        return $false
        }
    
    # Note on a common error:
    #New-Object : Exception calling ".ctor" with "0" argument(s): "Unexpected error launching Internet Explorer. Protected
    #Mode settings are not the same for all zones. Enable Protected Mode must be set to the same value (enabled or
    #disabled) for all zones. (SessionNotCreated)"
    #At line:1 char:15
    #+ $seleniumIe = New-Object "OpenQA.Selenium.IE.InternetExplorerDriver"
    #+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #    + CategoryInfo          : InvalidOperation: (:) [New-Object], MethodInvocationException
    #    + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand
    # Solution: either DISABLE or ENABLE Protected mode for ALL ZONES
}

function getMetalPrice{
    param(
        $metalName='gold',
        $metalPriceApi
    )
    $priceData=Invoke-RestMethod $metalPriceApi
    $price=[math]::round(($priceData.spreadProfilePrices.ask | Measure-Object -Average).Average,2)
    write-host "$(get-date) --- $metalName price is $price"
    return [hashtable]@{
        $metalName=$price
    }
}

function updateFieldValues{
    param(
        $ie,
        $fieldId,
        $value
    )
    $field=$ie.FindElementById($fieldId)
    $field.clear()
    $field.SendKeys($value)
}

function updateMetalPrices{
    Do{
        if($countDown -lt $timesToDeterminePause){
            $changesDetected=$false
            $prices=$null
            $title=$ie.Title
            if($title -notlike $expectedPageTitle){
                $null=get-process iexplore | stop-process -force
                $ie=autoLoginSe $goldPricePage $username $password
                if($ie.Title -ne $expectedPageTitle){$ie.Navigate().GoToURL($goldPricePage)}
                clear-Host
                write-host "Connected to $goldPricePage"
            }
            $goldPrice=getMetalPrice 'gold' $goldPriceApi
            $silverPrice=getMetalPrice 'silver' $silverPriceApi
            $platinumPrice=getMetalPrice 'platinum' $platinumApi
            $palladiumPrice=getMetalPrice 'palladium' $palladiumApi
            $prices=$goldPrice+$silverPrice+$platinumPrice+$palladiumPrice
            if(!$goldPrice){
                write-warning "Cannot update if gold price value is Null"
            }elseif($previousAuPrice -ne $goldPrice){
                updateFieldValues $ie $goldPriceFieldId $prices.gold
                $changesDetected=$true
            }else{
                write-host "Gold prices haven't changed."
            }
            if(!$silverPrice){
                write-warning "Cannot update if silver price value is Null"
            }elseif($previousAgPrice -ne $silverPrice){
                updateFieldValues $ie $agFieldId $prices.silver
                $changesDetected=$true
            }else{
                write-host "Silver prices haven't changed."
            }
            if(!$platinumPrice){
                write-warning "Cannot update if gold price value is Null"
            }elseif($previousPtPrice -ne $platinumPrice){
                updateFieldValues $ie $ptFieldId $prices.platinum
                $changesDetected=$true
            }else{
                write-host "Platinum prices haven't changed."
            }
            if(!$palladiumPrice){
                write-warning "Cannot update if gold price value is Null"
            }elseif($previousPdPrice -ne $palladiumPrice){
                updateFieldValues $ie $pdFieldId $prices.palladium
                $changesDetected=$true
            }else{
                write-host "Palladium prices haven't changed."
            }
            if($changesDetected){
                $submitButton=$ie.findElementByClassName($submitButtonClass)
                $submitButton.Click()
                $countDown=0
            }else{
                $countDown++
            }
            $previousAuPrice=$goldPrice
            $previousAgPrice=$silverPrice
            $previousPtPrice=$platinumPrice
            $previousPdPrice=$palladiumPrice
            write-host "time-out $updateSeconds seconds until next iteration"
            Start-Sleep -s $updateSeconds
        }else{
            Start-Sleep -s $pauseSeconds
            $countDown=0
        }
    }until($false)
}

updateMetalPrices

WordPress: Gold Price Widget CSS

Here’s a sample of CSS Code to customize the display of the WordPress’ Gold Pricing Plugin:

.widget_block {
  padding: 0px !important;
  margin: 0px !important;
  border: 0px !important;
  }
  .r1 {
  width: 24.9%  !important;
  }
  .r2 {
  width: 24.9%  !important;
  }
  .r3 {
  width: 24.9%  !important;
  }
  .r4 {
  width: 24.9%  !important;
  }
  .metal-price {
  animation: blinker 10s linear infinite;
  color: red !important;
  }
  @keyframes blinker {
    50% {
      opacity: 0;
    }
  }
  .date {
  background-color: white !important;
  color: black !important;
  }

Windows: Disable Automatic Restart After Updates

1. Run gpedit.msc as Administrator
2. Navigate to Local Group Policy Editor > Computer Configuration > Administrative Templates > Windows Components > Windows Update
3. Double-click on “No auto-restart with automatic installations of scheduled updates”
4. Select “Enabled”, and then click “OK”
5. Close the local group policy editor

PowerShell: Remove IP Address Assignment Using Bluecat API

$bluecatUri='http://bluecat.kimconnect.com/Services/API'
$bluecatUsername='svc-bluecat-api'
$bluecatPassword='PASSWORD'
$configId=17
$ipv4Address='10.10.162.54'
$marker='toBeDeleted-'

function confirmation($content,$testValue="I confirm",$maxAttempts=3){
  $confirmed=$false;
  $attempts=0;        
  $content|write-host
  write-host "Please review this content for accuracy.`r`n"
  while ($attempts -le $maxAttempts){
      if($attempts++ -ge $maxAttempts){
          write-host "A maximum number of attempts have reached. No confirmations received!`r`n"
          break;
          }
      $userInput = Read-Host -Prompt "Please type in this value => $testValue <= to confirm. Input CANCEL to skip this item";
      if ($userInput.ToLower() -eq $testValue.ToLower()){
          $confirmed=$true;
          write-host "Confirmed!`r`n";
          break;                
      }elseif($userInput -like 'cancel'){
          write-host 'Cancel command received.'
          $confirmed=$false
          break
      }else{
          cls;
          $content|write-host
          write-host "Attempt number $attempts of $maxAttempts`: $userInput does not match $testValue. Try again or Input CANCEL to skip this item`r`n"
          }
      }
  return $confirmed;
}
function loginBluecat{
  param(
    [Parameter(Mandatory=$true)]$uri,
    [Parameter(Mandatory=$true)]$username,
    [Parameter(Mandatory=$true)]$password
  )
  $proxy = New-WebServiceProxy -Uri "$($uri)?wsdl"
    $proxy.url = $uri
    $cookieContainer = New-Object System.Net.CookieContainer
    $proxy.CookieContainer = $cookieContainer
    $proxy.login($username, $password)
  return $proxy
}

function removeIpv4Assignment{
  param(
    [Parameter(Mandatory=$true)]$proxy,
    [Parameter(Mandatory=$true)]$configId,
    [Parameter(Mandatory=$true)]$ipV4Address,
    [string]$marker='toBeDeleted-'
  )
  
  $erroractionpreference='stop'
  try{    
    $record=$proxy.getIP4Address($configId,$ipV4Address)
    if($record.id -eq 0){
      write-host "IP Address $ipv4Address does not exist in config ID $configId"
      return -1
    }else{
      $markedRecord=$marker+$record.name
      $record.Name=$markedRecord
      $proxy.update($record)
      $property=$proxy.searchByObjectTypes($markedRecord, "IP4Address", 0, 1)
      $confirmed=confirmation "Delete this record:`r`n$(($property|out-string).trim())"
      if($confirmed){
        $proxy.delete($property.id)
        return 0
      }else{
        write-host "User cancelled operation. IP Address $ipv4Address NOT removed."
        return 1
      }      
    }
  }catch{
    write-warning $_
    return 1
  }  
}

$bluecatProxy=loginBluecat -Uri $bluecatUri -Username $bluecatUsername -Password $bluecatPassword
removeIpv4Assignment -proxy $bluecatProxy -configId $configId -ipv4Address $ipV4Address

PowerShell: Unjoin Computer From Domain

$computername='testwindows'
$adminUsername='intranet\testadmin'
$adminPassword='PASSWORD'
$workgroup='Archive'

function unjoinComputerFromDomain{
    param(
        $computername,
        $adminUsername,
        $adminPassword,
        $workgroup='Archive'
        )
  $adminCred=New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $adminUsername,$(ConvertTo-SecureString $adminPassword -AsPlainText -Force)
  $psSession=try{
    $psOptions=New-PSSessionOption -OpenTimeout 300 -CancelTimeout 300
    new-pssession -computername $computername -Credential $adminCred -SessionOption $psOptions
    write-host "Connected to $computername..."
  }catch{
    write-warning $_
    $false
  }
  if($psSession.State -eq 'Opened'){
    try{
        $result=invoke-command -session $psSession -scriptblock{
            param ($adminUsername,$adminPassword,$workgroup)
            if ((gwmi win32_computersystem).partofdomain -eq $true) {
                $userdomain=$env:USERDNSDOMAIN
                $encryptedPassword=$(ConvertTo-SecureString $adminPassword -AsPlainText -Force)
                Set-LocalUser -name Administrator -Password $encryptedPassword
                write-host "The local 'Administrator' account password has been reset to be the same as the password of user $adminUsername"
                $adminCred=New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $adminUsername,$encryptedPassword
                try{
                    Remove-Computer -UnjoinDomaincredential $adminCred -PassThru -Verbose -Restart -WorkgroupName $workgroup -Force
                    write-host "$env:computername has been removed from $userdomain"
                    return $true
                }catch{
                    write-warning $_
                    return $false
                }
            }else{
                write-host "$env:computer is NOT joined to any domain. No actions taken."
                return $true
            }      
        } -Args $adminUsername,$adminPassword,$workgroup -EA Stop
        $nullRemove-PSSession -ID $psSession.ID
        return $result
    }catch{
        write-warning $_
        $null=Remove-PSSession $psSession
        return $false
    }    
  }else{
    write-host "Unable to connect to $computername..."
    return $false
  }
}

unjoinComputerFromDomain $computername $adminUsername $adminPassword

Windows Server 2019 Remote Desktop Black Screen Problem

While there’s no definite solution to this issue, here are some options to work around this problem:

Option A:

  • Change the Screen Resolution slightly each time before clicking Connect
  • Disable bitmap caching for your RDP connections
  • Confirm that the target machine’s video drivers are up to date
  • Add the login account into the local Administrators group to test
  • Use a different RDP client (e.g. Remmina, mRemoteNG), instead of Windows’ default MSTSC.

Option B:

  • Install the latest Windows Updates
  • Reinstall the Server’s OS
  • If all else fails, hire guys like me to fix it

How to Use ImageMagic to Resize Images

Optimized Conversion:

# This is an optimized resizing command to convert PNG to JPG with minimal loss of image quality
outputPath=./resized
outputWidth=1200
mkdir $outputPath
originalFormat=png
newFormat=jpg
mogrify -path $outputPath -format $newFormat -resize $outputWidth -filter Triangle -define filter:support=2 -unsharp 0.25x0.25+8+0.065 -dither None -posterize 136 -quality 82 -define jpeg:fancy-upsampling=off -define png:compression-filter=5 -define png:compression-level=9 -define png:compression-strategy=1 -define png:exclude-chunk=all -interlace none -colorspace sRGB -strip *.$originalFormat

The 1-liner Command:

# This command would pickup all PNG images of current directory and create resized versions of them in the ./resized folder
mkdir ./resized
mogrify -path ./resized -resize 1200 *.png

This would happen if the resized directory doesn’t exist

kim@kim-linux:~$ cd ~
kim@kim-linux:/Scans$ mogrify -path ./resized -resize 1200 *.png 
mogrify-im6.q16: unable to open image `./resized//Military-Payment-Certificate-One-Dollar-Series-661-Backside.png': No such file or directory @ error/blob.c/OpenBlob/2874.
mogrify-im6.q16: WriteBlob Failed `./resized//Military-Payment-Certificate-One-Dollar-Series-661-Backside.png' @ error/png.c/MagickPNGErrorHandler/1641.

Excel Visual Basic For Application (VBA): Determine IP List

Set objExcel = CreateObject("Excel.Application")

objExcel.Visible = True

intRow = 2

 

Set Fso = CreateObject("Scripting.FileSystemObject")

Set objWorkbook = objExcel.Workbooks.Open("C:\File_Name.xls")

Set InputFile = objWorkbook

Do Until objExcel.Cells(intRow,1).Value = ""

strComputer = objExcel.Cells(intRow, 1).Value

 

objExcel.Cells(1, 1).Value = "Machine Name"

objExcel.Cells(1, 2).Value = "IP Address"

objExcel.Cells(1, 3).Value = "Status"

 

On Error Resume Next

Set objWMIService = GetObject("winmgmts:\\" &  strComputer & "\root\cimv2")

Set colItems = objWMIService.ExecQuery("Select * from Win32_NetworkAdapterConfiguration Where IPEnabled = True")

For Each objItem in colItems

If Err.Number <> 0 Then

objExcel.Cells(intRow, 2).Value = ""

objExcel.Cells(intRow, 3).Value = "Off Line"

Err.Clear

Else

objExcel.Cells(intRow, 2).Value = objItem.IPAddress

objExcel.Cells(intRow, 3).Value = "On Line"

End If

Next

intRow = intRow + 1

Loop

 

objExcel.Range("A1:C1").Select

objExcel.Selection.Interior.ColorIndex = 19

objExcel.Selection.Font.ColorIndex = 11

objExcel.Selection.Font.Bold = True

objExcel.Cells.EntireColumn.AutoFit

Set objWorkbook = Nothing

 

MsgBox "Done"

PowerShell: Function to Get Group Members as a Bypass Orphanated SID Errors

Problem:

[TESTSERVER]: PS C:\Users\administrator> Get-LocalGroupMember 'Remote Desktop Users'

Get-LocalGroupMember : Failed to compare two elements in the array.
    + CategoryInfo          : NotSpecified: (:) [Get-LocalGroupMember], InvalidOperationException
    + FullyQualifiedErrorId : An unspecified error occurred.,Microsoft.PowerShell.Commands.GetLocalGroupMemberCommand

[TESTSERVER]: PS C:\Users\administrator> Add-LocalGroupMember -group 'Remote Desktop Users' -member intranet\testuser
Add-LocalGroupMember : Principal intranet\testuser was not found.
    + CategoryInfo          : ObjectNotFound: (portal\nbsingh:String) [Add-LocalGroupMember], PrincipalNotFoundExcepti
   on
    + FullyQualifiedErrorId : PrincipalNotFound,Microsoft.PowerShell.Commands.AddLocalGroupMemberCommand

Workaround:

$servers = "localhost"
$groupName = 'Remote Desktop Users'

function getGroupMembers{
  param(
    $servers='localhost',
    $groupName='Administrators'
  )
  $members=@()
  foreach ($server in $servers){
      $group = [ADSI]"WinNT://$Server/$groupName"
      $groupMembers = @($group.Invoke('Members') | ForEach-Object {([ADSI]$_).path})
      $members+=$groupMembers
  }
  return $members
}

getGroupMembers $servers $groupName

CSS: How To Set Color Gradient and Animation to Text and Background

// Static color gradient
.colorGradientClass {
    background-color: #ffffff !important;
    background-image: linear-gradient(315deg, #ffffff 0%, #d9d9d9 74%)  !important;
}

// Animated color gradient
.colorTransitionClass {
    background-image: 
        linear-gradient(to right, transparent, white),
        linear-gradient(to right,yellow, white, yellow, white);
    background-size: 100% 100%, 2000% 100%;
    animation: move 5s infinite;
}
@keyframes move {
 from {background-position: center center, left center;}
 to {background-position: center center, right center;}
}

// This will result in blinking text
.blinkingText {
    animation: blinker 5s linear infinite;
    color: red !important;
}
@keyframes blinker {
  50% {
    opacity: 0;
  }
}

Uhhuh. NMI received for unknown reason 3d on CPU 4

Problem:

Message from syslogd@linux03 at Aug 31 04:28:21 ...
 kernel:[273033.123489] Uhhuh. NMI received for unknown reason 3d on CPU 4.

Message from syslogd@linux03 at Aug 31 04:28:21 ...
 kernel:[273033.123491] Do you have a strange power saving mode enabled?

Message from syslogd@linux03 at Aug 31 04:28:21 ...
 kernel:[273033.123491] Dazed and confused, but trying to continue

Solution: nothing… Error seems transient.

Ping Command’s First Packet Toward LDAP Server(s) Takes 2 Seconds to Start

Case 1: Are DNS servers working?
  • dig returns results right away => defined dns servers are working
  • dig returns results with a 2+ seconds delay or timeout => defined dns servers are NOT working

Recommendations:

  1. Test configuring client to use a different DNS server
    dig @dnsServer1.kimconnect.com ldapServerName
  2. Verify that routing and firewall rules are passing traffic from client to DNS servers
  3. Cleanup invalid DNS records in AD
Case 2: Is localhost able to cache hardware address?
  • apr -a command returns results right away, and the ldap server IP mac address is present => ARP is working fine
  • apr -a command takes awhile to populate => indication that localhost arp table is having issues, so it’s not caching mac to ip for fast lookups

Recommendations:

a. Add a static arp entry into localhost

Command:

arp -s ip-address-of-ldap-server hardware-address-of-ldap-server
# Example:
sudo arp -s 10.10.10.10 aa:11:bb:22:cc:44

# How to reverse the change:
sudo arp --delete 10.10.10.10

# How to check the ARP Table:
sudo arp -avn # more verbose
sudo arp -n # simple view

b. Clear ARP cash & DNS cache

ip -s -s neigh flush all
arp -n
service nscd restart

How To Install Graylog in a Kubernetes Cluster Using Helm Charts

The following narrative is based on the assumption that a Kubernetes (current stable version 20.10) has been setup using MetalLB Ingress controller. This should also work with Traefik or other load balancers.

# Create a separate namespace for this project
kubectl create namespace graylog

# Change into the graylog namespace
kubectl config set-context --current --namespace=graylog
kubectl config view --minify | grep namespace: # Validate it

# Optional: delete previous test instances of graylog that have been deployed via Helm
helm delete "graylog" --namespace graylog
kubectl delete pvc --namespace graylog --all

# How to switch execution context back to the 'default' namespace
kubectl config set-context --current --namespace=default

# Optional: installing mongdb prior to Graylog
helm install "mongodb" bitnami/mongodb --namespace "graylog" \
  --set persistence.size=100Gi
# Sample output:
NAME: mongodb
LAST DEPLOYED: Thu Aug 29 00:07:36 2021
NAMESPACE: graylog
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
** Please be patient while the chart is being deployed **
MongoDB&reg; can be accessed on the following DNS name(s) and ports from within your cluster:
    mongodb.graylog.svc.cluster.local
To get the root password run:
    export MONGODB_ROOT_PASSWORD=$(kubectl get secret --namespace graylog mongodb -o jsonpath="{.data.mongodb-root-password}" | base64 --decode)
To connect to your database, create a MongoDB&reg; client container:
    kubectl run --namespace graylog mongodb-client --rm --tty -i --restart='Never' --env="MONGODB_ROOT_PASSWORD=$MONGODB_ROOT_PASSWORD" --image docker.io/bitnami/mongodb:4.4.8-debian-10-r9 --command -- bash
Then, run the following command:
    mongo admin --host "mongodb" --authenticationDatabase admin -u root -p $MONGODB_ROOT_PASSWORD
To connect to your database from outside the cluster execute the following commands:
    kubectl port-forward --namespace graylog svc/mongodb 27017:27017 &
    mongo --host 127.0.0.1 --authenticationDatabase admin -p $MONGODB_ROOT_PASSWORD

# REQUIRED: Pre-install ElasticSearch version 7.10 as highest being supported by Graylog 4.1.3
# Source: https://artifacthub.io/packages/helm/elastic/elasticsearch/7.10.2
helm repo add elastic https://helm.elastic.co
helm repo update
helm install elasticsearch elastic/elasticsearch --namespace "graylog" \
  --set imageTag=7.10.2 \
  --set data.persistence.size=100Gi
# Sample output:
NAME: elasticsearch
LAST DEPLOYED: Sun Aug 29 04:35:30 2021
NAMESPACE: graylog
STATUS: deployed
REVISION: 1
NOTES:
1. Watch all cluster members come up.
  $ kubectl get pods --namespace=graylog -l app=elasticsearch-master -w
2. Test cluster health using Helm test.
  $ helm test elasticsearch

# Installation of Graylog with mongodb bundled, while integrating with a pre-deployed elasticSearch instance
#
# This install command assumes that the protocol preference for transporting logs is TCP
# Also, the current helm chart does not allow mixing TCP with UDP; therefore, this approach is conveniently
# matching business requirements where a reliable transmission TCP protocol is necessary to record security data.
helm install graylog kongz/graylog --namespace "graylog" \
  --set graylog.image.repository="graylog/graylog:4.1.3-1" \
  --set graylog.persistence.size=200Gi \
  --set graylog.service.type=LoadBalancer \
  --set graylog.service.port=80 \
  --set graylog.service.loadBalancerIP=10.10.100.88 \
  --set graylog.service.externalTrafficPolicy=Local \
  --set graylog.service.ports[0].name=gelf \
  --set graylog.service.ports[0].port=12201 \
  --set graylog.service.ports[1].name=syslog \
  --set graylog.service.ports[1].port=514 \
  --set graylog.rootPassword="SOMEPASSWORD" \
  --set tags.install-elasticsearch=false \
  --set graylog.elasticsearch.version=7 \
  --set graylog.elasticsearch.hosts=http://elasticsearch-master.graylog.svc.cluster.local:9200

# Optional: add these lines if the mongodb component has been installed separately
  --set tags.install-mongodb=false \
  --set graylog.mongodb.uri=mongodb://mongodb-mongodb-replicaset-0.mongodb-mongodb-replicaset.graylog.svc.cluster.local:27017/graylog?replicaSet=rs0 \

# Moreover, the graylog chart version 1.8.4 doesn't seem to set externalTrafficPolicy as expected.
# Set externalTrafficPolicy = local to preserve source client IPs
kubectl patch svc graylog-web -n graylog -p '{"spec":{"externalTrafficPolicy":"Local"}}'

# Sometimes, the static EXTERNAL-IP would be assigned to graylog-master, where graylog-web EXTERNAL-IP would
# remain in the status of <pending> indefinitely.
# Workaround: set services to share a single external IP
kubectl patch svc graylog-web -p '{"metadata":{"annotations":{"metallb.universe.tf/allow-shared-ip":"graylog"}}}'
kubectl patch svc graylog-master -p '{"metadata":{"annotations":{"metallb.universe.tf/allow-shared-ip":"graylog"}}}'
kubectl patch svc graylog-master -n graylog -p '{"spec": {"type": "LoadBalancer", "externalIPs":["10.10.100.88"]}}'
kubectl patch svc graylog-web -n graylog -p '{"spec": {"type": "LoadBalancer", "externalIPs":["10.10.100.88"]}}'

# Test sending logs to server via TCP
graylog-server=graylog.kimconnect.com
echo -e '{"version": "1.1","host":"kimconnect.com","short_message":"Short message","full_message":"This is a\n\nlong message","level":9000,"_user_id":9000,"_ip_address":"1.1.1.1","_location":"LAX"}\0' | nc -w 1 $graylog-server 514

# Test via UDP
graylog-server=graylog.kimconnect.com
echo -e '{"version": "1.1","host":"kimconnect.com","short_message":"Short message","full_message":"This is a\n\nlong message","level":9000,"_user_id":9000,"_ip_address":"1.1.1.1","_location":"LAX"}\0' | nc -u -w 1 $graylog-server 514

# Optional: graylog Ingress
cat > graylog-ingress.yaml <<EOF
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
  name: graylog-ingress
  namespace: graylog
  annotations:
    kubernetes.io/ingress.class: "nginx"
    # set these for SSL
    # ingress.kubernetes.io/rewrite-target: /
    # acme http01
    # acme.cert-manager.io/http01-edit-in-place: "true"
    # acme.cert-manager.io/http01-ingress-class: "true"
    # kubernetes.io/tls-acme: "true"  
spec:
  rules:
  - host: graylog.kimconnect.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: graylog-web
            port:
              number: 80
      - path: /
        pathType: Prefix
        backend:
          service:
            name: graylog-web
            port:
              number: 12201
      - path: /
        pathType: Prefix
        backend:
          service:
            name: graylog-web
            port:
              number: 514              
EOF
kubectl apply -f graylog-ingress.yaml

Troubleshooting Notes:

# Sample commands to patch graylog service components
kubectl patch svc graylog-web -p '{"spec":{"type":"LoadBalancer"}}' # Convert ClusterIP to LoadBalancer to gain ingress
kubectl patch svc graylog-web -p '{"spec":{"externalIPs":["10.10.100.88"]}}' # Add externalIPs
kubectl patch svc graylog-master -n graylog -p '{"spec":{"loadBalancerIP":""}}' # Remove loadBalancer IPs
kubectl patch svc graylog-master -n graylog -p '{"status":{"loadBalancer":{"ingress":[]}}}' # Purge ingress IPs
kubectl patch svc graylog-web -n graylog -p '{"status":{"loadBalancer":{"ingress":[{"ip":"10.10.100.88"}]}}}'
kubectl patch svc graylog-web -n graylog -p '{"status":{"loadBalancer":{"ingress":[]}}}'

# Alternative solution: mixing UDP with TCP
# The current chart version only allows this when service Type = ClusterIP (default)
helm upgrade graylog kongz/graylog --namespace "graylog" \
  --set graylog.image.repository="graylog/graylog:4.1.3-1" \
  --set graylog.persistence.size=200Gi \
  --set graylog.service.externalTrafficPolicy=Local \
  --set graylog.service.port=80 \
  --set graylog.service.ports[0].name=gelf \
  --set graylog.service.ports[0].port=12201 \
  --set graylog.service.ports[0].protocol=UDP \
  --set graylog.service.ports[1].name=syslog \
  --set graylog.service.ports[1].port=514 \
  --set graylog.service.ports[1].protocol=UDP \
  --set graylog.rootPassword="SOMEPASSWORD" \
  --set tags.install-elasticsearch=false \
  --set graylog.elasticsearch.version=7 \
  --set graylog.elasticsearch.hosts=http://elasticsearch-master.graylog.svc.cluster.local:9200

# Error message occurs when combing TCP with UDP; hence, a ClusterIP must be specified
Error: UPGRADE FAILED: cannot patch "graylog-web" with kind Service: Service "graylog-web" is invalid: spec.ports: Invalid value: []core.ServicePort{core.ServicePort{Name:"graylog", Protocol:"TCP", AppProtocol:(*string)(nil), Port:80, TargetPort:intstr.IntOrString{Type:0, IntVal:9000, StrVal:""}, NodePort:32518}, core.ServicePort{Name:"gelf", Protocol:"UDP", AppProtocol:(*string)(nil), Port:12201, TargetPort:intstr.IntOrString{Type:0, IntVal:12201, StrVal:""}, NodePort:0}, core.ServicePort{Name:"gelf2", Protocol:"TCP", AppProtocol:(*string)(nil), Port:12222, TargetPort:intstr.IntOrString{Type:0, IntVal:12222, StrVal:""}, NodePort:31523}, core.ServicePort{Name:"syslog", Protocol:"TCP", AppProtocol:(*string)(nil), Port:514, TargetPort:intstr.IntOrString{Type:0, IntVal:514, StrVal:""}, NodePort:31626}}: may not contain more than 1 protocol when type is 'LoadBalancer'

# Set array type value instead of string
Error: UPGRADE FAILED: error validating "": error validating data: ValidationError(Service.spec.externalIPs): invalid type for io.k8s.api.core.v1.ServiceSpec.externalIPs: got "string", expected "array"
# Solution:
--set "array={a,b,c}" OR --set service[0].port=80

# Graylog would not start and this was the error:
com.github.joschi.jadconfig.ValidationException: Parent directory /usr/share/graylog/data/journal for Node ID file at /usr/share/graylog/data/journal/node-id is not writable

# Workaround
graylogData=/mnt/k8s/graylog-journal-graylog-0-pvc-04dd9c7f-a771-4041-b549-5b4664de7249/
chown -fR 1100:1100 $graylogData

NAME: graylog
LAST DEPLOYED: Thu Aug 29 03:26:00 2021
NAMESPACE: graylog
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
To connect to your Graylog server:
1. Get the application URL by running these commands:
  Graylog Web Interface uses JavaScript to get detail of each node. The client JavaScript cannot communicate to node when service type is `ClusterIP`.
  If you want to access Graylog Web Interface, you need to enable Ingress.
    NOTE: Port Forward does not work with web interface.
2. The Graylog root users
  echo "User: admin"
  echo "Password: $(kubectl get secret --namespace graylog graylog -o "jsonpath={.data['graylog-password-secret']}" | base64 --decode)"
To send logs to graylog:
  NOTE: If `graylog.input` is empty, you cannot send logs from other services. Please make sure the value is not empty.
        See https://github.com/KongZ/charts/tree/main/charts/graylog#input for detail

k describe pod graylog-0
Events:
  Type     Reason            Age                   From               Message
  ----     ------            ----                  ----               -------
  Warning  FailedScheduling  11m                   default-scheduler  0/4 nodes are available: 4 pod has unbound immediate PersistentVolumeClaims.
  Warning  FailedScheduling  11m                   default-scheduler  0/4 nodes are available: 4 pod has unbound immediate PersistentVolumeClaims.
  Normal   Scheduled         11m                   default-scheduler  Successfully assigned graylog/graylog-0 to linux03
  Normal   Pulled            11m                   kubelet            Container image "alpine" already present on machine
  Normal   Created           11m                   kubelet            Created container setup
  Normal   Started           10m                   kubelet            Started container setup
  Normal   Started           4m7s (x5 over 10m)    kubelet            Started container graylog-server
  Warning  Unhealthy         3m4s (x4 over 9m14s)  kubelet            Readiness probe failed: Get "http://172.16.90.197:9000/api/system/lbstatus": dial tcp 172.16.90.197:9000: connect: connection refused
  Normal   Pulled            2m29s (x6 over 10m)   kubelet            Container image "graylog/graylog:4.1.3-1" already present on machine
  Normal   Created           2m19s (x6 over 10m)   kubelet            Created container graylog-server
  Warning  BackOff           83s (x3 over 2m54s)   kubelet            Back-off restarting failed container

Readiness probe failed: Get http://api/system/lbstatus: dial tcp 172.16.90.197:9000: connect: connection refused

# Set external IP
# This only works on LoadBalancer, not ClusterIP
# kubectl patch svc graylog-web -p '{"spec":{"externalIPs":["10.10.100.88"]}}'
# kubectl patch svc graylog-master -p '{"spec":{"externalIPs":[]}}'

kubectl patch service graylog-web --type='json' -p='[{"op": "add", "path": "/metadata/annotations/kubernetes.io~1ingress.class", "value":"nginx"}]'

# Set annotation to allow shared IPs between 2 different services
kubectl annotate service graylog-web metallb.universe.tf/allow-shared-ip=graylog
kubectl annotate service graylog-master metallb.universe.tf/allow-shared-ip=graylog

metadata:
  name: $serviceName-tcp
  annotations:
    metallb.universe.tf/address-pool: default
    metallb.universe.tf/allow-shared-ip: psk

# Ingress
appName=graylog
domain=graylog.kimconnect.com
deploymentName=graylog-web
containerPort=9000
cat <<EOF> $appName-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: $appName-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    # ingress.kubernetes.io/rewrite-target: /
    # acme http01
    # acme.cert-manager.io/http01-edit-in-place: "true"
    # acme.cert-manager.io/http01-ingress-class: "true"
    # kubernetes.io/tls-acme: "true"
spec:
  rules:
  - host: $domain
    http:
      paths:
      - backend:
          service:
            name: $deploymentName
            port:
              number: 9000
        path: /
        pathType: Prefix
EOF
kubectl apply -f $appName-ingress.yaml

# delete pvc's
namespace=graylog
kubectl delete pvc data-graylog-elasticsearch-data-0 -n $namespace
kubectl delete pvc data-graylog-elasticsearch-master-0 -n $namespace
kubectl delete pvc datadir-graylog-mongodb-0 -n $namespace
kubectl delete pvc journal-graylog-0 -n $namespace

# delete all pvc's in namespace the easier way
namespace=graylog
kubectl get pvc -n $namespace | awk '$1 {print$1}' | while read vol; do kubectl delete pvc/${vol} -n $namespace; done

2021-08-20 20:19:41,048 INFO    [cluster] - Exception in monitor thread while connecting to server mongodb-mongodb-replicaset-0.mongodb-mongodb-replicaset.graylog.svc.cluster.local:27017 - {}
com.mongodb.MongoSocketException: mongodb-mongodb-replicaset-0.mongodb-mongodb-replicaset.graylog.svc.cluster.local
        at com.mongodb.ServerAddress.getSocketAddresses(ServerAddress.java:211) ~[graylog.jar:?]
        at com.mongodb.internal.connection.SocketStream.initializeSocket(SocketStream.java:75) ~[graylog.jar:?]
        at com.mongodb.internal.connection.SocketStream.open(SocketStream.java:65) ~[graylog.jar:?]
        at com.mongodb.internal.connection.InternalStreamConnection.open(InternalStreamConnection.java:128) ~[graylog.jar:?]
        at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:117) [graylog.jar:?]
        at java.lang.Thread.run(Thread.java:748) [?:1.8.0_302]
Caused by: java.net.UnknownHostException: mongodb-mongodb-replicaset-0.mongodb-mongodb-replicaset.graylog.svc.cluster.local
        at java.net.InetAddress.getAllByName0(InetAddress.java:1281) ~[?:1.8.0_302]
        at java.net.InetAddress.getAllByName(InetAddress.java:1193) ~[?:1.8.0_302]
        at java.net.InetAddress.getAllByName(InetAddress.java:1127) ~[?:1.8.0_302]
        at com.mongodb.ServerAddress.getSocketAddresses(ServerAddress.java:203) ~[graylog.jar:?]
        ... 5 more

2021-08-20 20:19:42,981 INFO    [cluster] - No server chosen by com.mongodb.client.internal.MongoClientDelegate$1@69419d59 from cluster description ClusterDescription{type=REPLICA_SET, connectionMode=MULTIPLE, serverDescriptions=[ServerDescription{address=mongodb-mongodb-replicaset-0.mongodb-mongodb-replicaset.graylog.svc.cluster.local:27017, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketException: mongodb-mongodb-replicaset-0.mongodb-mongodb-replicaset.graylog.svc.cluster.local}, caused by {java.net.UnknownHostException: mongodb-mongodb-replicaset-0.mongodb-mongodb-replicaset.graylog.svc.cluster.local}}]}. Waiting for 30000 ms before timing out - {}

# Alternative version - that doesn't work
# helm repo add groundhog2k https://groundhog2k.github.io/helm-charts/
# helm install graylog groundhog2k/graylog --namespace "graylog" \
#   --set image.tag=4.1.3-1 \
#   --set settings.http.publishUri='http://127.0.0.1:9000/' \
#   --set service.type=LoadBalancer \
#   --set service.loadBalancerIP=192.168.100.88 \
#   --set elasticsearch.enabled=true \
#   --set mongodb.enabled=true

# helm upgrade graylog groundhog2k/graylog --namespace "graylog" \
#   --set image.tag=4.1.3-1 \
#   --set settings.http.publishUri=http://localhost:9000/ \
#   --set service.externalTrafficPolicy=Local \
#   --set service.type=LoadBalancer \
#   --set service.loadBalancerIP=192.168.100.88 \
#   --set elasticsearch.enabled=true \
#   --set mongodb.enabled=true \
#   --set storage.className=nfs-client \
#   --set storage.requestedSize=200Gi

# kim@linux01:~$ k logs graylog-0
# 2021-08-29 03:47:09,345 ERROR: org.graylog2.bootstrap.CmdLineTool - Invalid configuration
# com.github.joschi.jadconfig.ValidationException: Couldn't run validator method
#         at com.github.joschi.jadconfig.JadConfig.invokeValidatorMethods(JadConfig.java:227) ~[graylog.jar:?]
#         at com.github.joschi.jadconfig.JadConfig.process(JadConfig.java:100) ~[graylog.jar:?]
#         at org.graylog2.bootstrap.CmdLineTool.processConfiguration(CmdLineTool.java:420) [graylog.jar:?]
#         at org.graylog2.bootstrap.CmdLineTool.run(CmdLineTool.java:236) [graylog.jar:?]
#         at org.graylog2.bootstrap.Main.main(Main.java:45) [graylog.jar:?]
# Caused by: java.lang.reflect.InvocationTargetException
#         at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_302]
#         at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_302]
#         at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_302]
#         at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_302]
#         at com.github.joschi.jadconfig.ReflectionUtils.invokeMethodsWithAnnotation(ReflectionUtils.java:53) ~[graylog.jar:?]
#         at com.github.joschi.jadconfig.JadConfig.invokeValidatorMethods(JadConfig.java:221) ~[graylog.jar:?]
#         ... 4 more
# Caused by: java.lang.IllegalArgumentException: URLDecoder: Illegal hex characters in escape (%) pattern - For input string: "!s"
#         at java.net.URLDecoder.decode(URLDecoder.java:194) ~[?:1.8.0_302]
#         at com.mongodb.ConnectionString.urldecode(ConnectionString.java:1035) ~[graylog.jar:?]
#         at com.mongodb.ConnectionString.urldecode(ConnectionString.java:1030) ~[graylog.jar:?]
#         at com.mongodb.ConnectionString.<init>(ConnectionString.java:336) ~[graylog.jar:?]
#         at com.mongodb.MongoClientURI.<init>(MongoClientURI.java:256) ~[graylog.jar:?]
#         at org.graylog2.configuration.MongoDbConfiguration.getMongoClientURI(MongoDbConfiguration.java:59) ~[graylog.jar:?]
#         at org.graylog2.configuration.MongoDbConfiguration.validate(MongoDbConfiguration.java:64) ~[graylog.jar:?]
#         at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_302]
#         at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_302]
#         at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_302]
#         at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_302]
#         at com.github.joschi.jadconfig.ReflectionUtils.invokeMethodsWithAnnotation(ReflectionUtils.java:53) ~[graylog.jar:?]
#         at com.github.joschi.jadconfig.JadConfig.invokeValidatorMethods(JadConfig.java:221) ~[graylog.jar:?]

How to configure Ubiquiti EdgeRouter to send logs to a Syslog Server

Method 1: using text editor

# Edit the syslog config
sudo vi /etc/rsyslog.d/vyatta-log.conf

# Change the @ = udp symbol to @@ = tcp
# add :PORTNUMBER after node name or IP if necessary
admin@EdgeRouter-4:~$ cat /etc/rsyslog.d/vyatta-log.conf
*.err	@graylog.kimconnect.com
*.notice;local7.debug	-/var/log/messages

Method 2: use sed to update texts

# Change from udp to tcp
sudo sed 's/@/@@/' -i /etc/rsyslog.d/vyatta-log.conf
cat /etc/rsyslog.d/vyatta-log.conf

# Change from tcp to udp
sudo sed 's/@@/@/' -i /etc/rsyslog.d/vyatta-log.conf
cat /etc/rsyslog.d/vyatta-log.conf

# Restart syslogd
sudo service rsyslog restart

How to Setup Dynamic DNS with Google Domains & Ubiquity EdgeRouter

Step 1: Set up Dynamic DNS

– Access Google Domains: https://domains.google.com/registrar/
– Click on the Manage button, next to your domain
– Click on DNS
– Scroll toward the bottom to click on Advanced Settings
– Click on Manage dynamic DNS
– Leave the hostname field blank, click on Save
– If this domain already has a record, click on Replace to proceed or Cancel to input a different sub-domain
– Click on the drop-down menu next to ‘Your domain has Dynamic DNS setup’
– Select View credentials to trigger a pop-up window
– Click on View to see the username and password generated for this domain
– Copy and paste the information into a notepad to be used in ‘Step 2’
– Select Close

Step 2: Configure EdgeRouter with Dynamic DNS

– Access the router: https://ip.address.of.router/#Services/DNS
– In section Dynamic DNS, click the Add Dynamic DNS Interface button
– Set these values:
  – Interface: eth0 (or WAN interface)
  – Web: <leave blank>
  – Web-skip: <leave blank>
  – Service: dyndns
  – Hostname: kimconnect.com (or the hostname that has been setup in step 1)
  – Login: {username copied in step 1}
  – Password: {password copied in step 1}
  – Protocol: dyndns2
  – Server: domains.google.com
– Click on Apply
– Click on Force Update to expect this message ‘The configuration has been applied successfully’

How To Configure Alternative Storage for a Kubernetes (K8s) Worker Node

The below illustration is assuming that one has a local RAID mount being added to a worker node due to it’s lack of local storage to run kubelets and docker containers

# On K8s controller, remove worker node
kubectl drain node linux03 --ignore-damonsets
kubectl delete node linux03

# On the worker node uninstall docker & kubelet
sudo apt-get remove docker-ce docker-ce-cli containerd.io kubelet

# Check the health of its RAID mount /dev/md0
mdadm --detail /dev/md0

# Sample expected output:
           Version : 1.2
     Creation Time : Fri Aug 13 23:46:13 2021
        Raid Level : raid10
        Array Size : 1953257472 (1862.77 GiB 2000.14 GB)
     Used Dev Size : 976628736 (931.39 GiB 1000.07 GB)
      Raid Devices : 4
     Total Devices : 4
       Persistence : Superblock is persistent
     Intent Bitmap : Internal
       Update Time : Sat Aug 28 23:39:08 2021
             State : clean
    Active Devices : 4
   Working Devices : 4
    Failed Devices : 0
     Spare Devices : 0
            Layout : near=2
        Chunk Size : 512K
Consistency Policy : bitmap
              Name : linux03:0  (local to host linux03)
              UUID : 
            Events : 1750
    Number   Major   Minor   RaidDevice State
       0       8       97        0      active sync set-A   /dev/sdg1
       1       8       81        1      active sync set-B   /dev/sdf1
       2       8       17        2      active sync set-A   /dev/sdb1
       3       8        1        3      active sync set-B   /dev/sda1

# Check the logical mount
mount=/nfs-share
df -hT -P $mount

# Sample expected output:
root@linux03:/home/kimconnect# df -hT -P $mount
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/md0       ext4  1.8T   77M  1.7T   1% /nfs-share

# Prepare docker & kubelet redirected links
source1=/nfs-share/linux03/docker
source2=/nfs-share/linux03/kubelet
destinationdirectory=/var/lib/
sudo mkdir -p $source1
sudo mkdir -p $source2

# Optional: remove existing docker & kubelet directories
rm -rf /var/lib/kubelet
rm -rf /var/lib/docker

# Create links
sudo ln -sfn $source1 $destinationdirectory
sudo ln -sfn $source2 $destinationdirectory

# Verify
ls -la /var/lib

# Expected output:
root@linux03:/home/kim# ls /var/lib -la
total 180
drwxr-xr-x 45 root      root      4096 Aug 28 00:38 .
drwxr-xr-x 13 root      root      4096 Feb  1  2021 ..
drwxr-xr-x  4 root      root      4096 Feb  1  2021 AccountsService
drwxr-xr-x  5 root      root      4096 Aug 28 00:24 apt
drwxr-xr-x  2 root      root      4096 Sep 10  2020 boltd
drwxr-xr-x  2 root      root      4096 Aug 27 21:21 calico
drwxr-xr-x  8 root      root      4096 Aug 28 00:34 cloud
drwxr-xr-x  4 root      root      4096 Aug 27 23:52 cni
drwxr-xr-x  2 root      root      4096 Aug 27 19:38 command-not-found
drwx--x--x 11 root      root      4096 Aug 27 20:24 containerd
drwxr-xr-x  2 root      root      4096 Aug 27 19:57 dbus
drwxr-xr-x  2 root      root      4096 Apr 10  2020 dhcp
lrwxrwxrwx  1 root      root        25 Aug 27 23:24 docker -> /nfs-share/linux03/docker
drwxr-xr-x  3 root      root      4096 Aug 27 21:15 dockershim
drwxr-xr-x  7 root      root      4096 Aug 28 00:24 dpkg
drwxr-xr-x  3 root      root      4096 Feb  1  2021 fwupd
drwxr-xr-x  2 root      root      4096 Apr 20  2020 git
drwxr-xr-x  4 root      root      4096 Aug 27 19:39 grub
drwxr-xr-x  2 root      root      4096 Aug 27 19:51 initramfs-tools
lrwxrwxrwx  1 root      root        26 Aug 28 00:38 kubelet -> /nfs-share/linux03/kubelet
### truncated for brevity ###

# Reinstall docker & kubernetes
version=1.20.10-00
apt-get install -qy --allow-downgrades --allow-change-held-packages kubeadm=$version kubelet=$version kubectl=$version docker-ce docker-ce-cli containerd.io nfs-common
apt-mark hold kubeadm kubelet kubectl

I may consider making another illustration for NFS mounts. It may not be necessary as the instructions would be mostly similar. The difference would be that one must ensure that the worker node automatically mounts the nfs share upon reboots. The command to make symbolic soft-links would be the same.

Ubuntu: Auto Updates Configuration

Prepare the Linux OS:

# Install auto-update packages
sudo apt install -y unattended-upgrades apt-listchanges

# Configure unattended updates
sudo dpkg-reconfigure -plow unattended-upgrades
Answering ‘Yes’ to allow unattended-upgrades

Edit the auto-upgrade file:
sudo vim /etc/apt/apt.conf.d/50unattended-upgrades

// Automatically upgrade packages from these (origin:archive) pairs
//
// Note that in Ubuntu security updates may pull in new dependencies
// from non-security sources (e.g. chromium). By allowing the release
// pocket these get automatically pulled in.
Unattended-Upgrade::Allowed-Origins {
	"${distro_id}:${distro_codename}";
	"${distro_id}:${distro_codename}-security";
	// Extended Security Maintenance; doesn't necessarily exist for
	// every release and this system may not have it installed, but if
	// available, the policy for updates is such that unattended-upgrades
	// should also install from here by default.
	"${distro_id}ESMApps:${distro_codename}-apps-security";
	"${distro_id}ESM:${distro_codename}-infra-security";
//	"${distro_id}:${distro_codename}-updates";
//	"${distro_id}:${distro_codename}-proposed";
//	"${distro_id}:${distro_codename}-backports";
};

// Python regular expressions, matching packages to exclude from upgrading
Unattended-Upgrade::Package-Blacklist {
    // The following matches all packages starting with linux-
//  "linux-";

    // Use $ to explicitely define the end of a package name. Without
    // the $, "libc6" would match all of them.
//  "libc6$";
//  "libc6-dev$";
//  "libc6-i686$";

    // Special characters need escaping
//  "libstdc\+\+6$";

    // The following matches packages like xen-system-amd64, xen-utils-4.1,
    // xenstore-utils and libxenstore3.0
//  "(lib)?xen(store)?";

    // For more information about Python regular expressions, see
    // https://docs.python.org/3/howto/regex.html
};

// This option controls whether the development release of Ubuntu will be
// upgraded automatically. Valid values are "true", "false", and "auto".
Unattended-Upgrade::DevRelease "auto";

// This option allows you to control if on a unclean dpkg exit
// unattended-upgrades will automatically run 
//   dpkg --force-confold --configure -a
// The default is true, to ensure updates keep getting installed
//Unattended-Upgrade::AutoFixInterruptedDpkg "true";

// Split the upgrade into the smallest possible chunks so that
// they can be interrupted with SIGTERM. This makes the upgrade
// a bit slower but it has the benefit that shutdown while a upgrade
// is running is possible (with a small delay)
//Unattended-Upgrade::MinimalSteps "true";

// Install all updates when the machine is shutting down
// instead of doing it in the background while the machine is running.
// This will (obviously) make shutdown slower.
// Unattended-upgrades increases logind's InhibitDelayMaxSec to 30s.
// This allows more time for unattended-upgrades to shut down gracefully
// or even install a few packages in InstallOnShutdown mode, but is still a
// big step back from the 30 minutes allowed for InstallOnShutdown previously.
// Users enabling InstallOnShutdown mode are advised to increase
// InhibitDelayMaxSec even further, possibly to 30 minutes.
//Unattended-Upgrade::InstallOnShutdown "false";

// Send email to this address for problems or packages upgrades
// If empty or unset then no email is sent, make sure that you
// have a working mail setup on your system. A package that provides
// 'mailx' must be installed. E.g. "user@example.com"
//Unattended-Upgrade::Mail "";

// Set this value to one of:
//    "always", "only-on-error" or "on-change"
// If this is not set, then any legacy MailOnlyOnError (boolean) value
// is used to chose between "only-on-error" and "on-change"
//Unattended-Upgrade::MailReport "on-change";

// Remove unused automatically installed kernel-related packages
// (kernel images, kernel headers and kernel version locked tools).
//Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";

// Do automatic removal of newly unused dependencies after the upgrade
//Unattended-Upgrade::Remove-New-Unused-Dependencies "true";

// Do automatic removal of unused packages after the upgrade
// (equivalent to apt-get autoremove)
//Unattended-Upgrade::Remove-Unused-Dependencies "false";

// Automatically reboot *WITHOUT CONFIRMATION* if
//  the file /var/run/reboot-required is found after the upgrade
//Unattended-Upgrade::Automatic-Reboot "false";

// Automatically reboot even if there are users currently logged in
// when Unattended-Upgrade::Automatic-Reboot is set to true
//Unattended-Upgrade::Automatic-Reboot-WithUsers "true";

// If automatic reboot is enabled and needed, reboot at the specific
// time instead of immediately
//  Default: "now"
//Unattended-Upgrade::Automatic-Reboot-Time "02:00";

// Use apt bandwidth limit feature, this example limits the download
// speed to 70kb/sec
//Acquire::http::Dl-Limit "70";

// Enable logging to syslog. Default is False
// Unattended-Upgrade::SyslogEnable "false";

// Specify syslog facility. Default is daemon
// Unattended-Upgrade::SyslogFacility "daemon";

// Download and install upgrades only on AC power
// (i.e. skip or gracefully stop updates on battery)
// Unattended-Upgrade::OnlyOnACPower "true";

// Download and install upgrades only on non-metered connection
// (i.e. skip or gracefully stop updates on a metered connection)
// Unattended-Upgrade::Skip-Updates-On-Metered-Connections "true";

// Verbose logging
// Unattended-Upgrade::Verbose "false";

// Print debugging information both in unattended-upgrades and
// in unattended-upgrade-shutdown
// Unattended-Upgrade::Debug "false";

// Allow package downgrade if Pin-Priority exceeds 1000
// Unattended-Upgrade::Allow-downgrade "false";

Verify:

sudo unattended-upgrades --dry-run