PowerShell: Connect to Office 365 Exchange Online

function disconnectExchangeOnline{
<# manually check sessions
PS C:\Windows\system32> Get-PSSession

Id Name ComputerName ComputerType State ConfigurationName Availability
-- ---- ------------ ------------ ----- ----------------- ------------
4 WinRM4 outlook.offi... RemoteMachine Opened Microsoft.Exchange Available
5 WinRM5 outlook.offi... RemoteMachine Opened Microsoft.Exchange Available
#>
$activeExchangeOnlineSessionIds=(Get-PSSession |?{$_.ConfigurationName -eq "Microsoft.Exchange"}).Id
if($activeExchangeOnlineSessionIds){
Remove-PSSession -id $activeExchangeOnlineSessionIds;
write-host "session ID $activeExchangeOnlineSessionIds is disconnected."
}
}

function connectToExchangeOnline{
param(
$credential=(get-credential),
$connectionURI="https://outlook.office365.com/powershell-liveid/"
)
#Disconnect any active sessions prior to initiating a new one
disconnectExchangeOnline;

if (!($activeExchangeOnlineSessionIds)){
#Imports the base MSOnline module
if (!(Get-Module -ListAvailable -Name MSOnline)){Install-Module MSOnline -Confirm:$false -Force;}

$GLOBAL:onlineExchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri $connectionURI -Credential $credential -Authentication Basic -AllowRedirection
Import-PSSession $onlineExchangeSession -AllowClobber -DisableNameChecking
}else{
write-host "An active session is already available. No new connections were made."
}
}

connectToExchangeOnline;

PowerShell: Get Executable Version and File Location

# Quick method to obtain computernames of all nodes in a cluster and adjoin result with standalone machine names
$standaloneFileServers="komputer1","komputer2"
$sourceClusters="cluster1","cluster2"
$nodes=$sourceClusters|%{get-clusternode -cluster $_}
$computerNames=$nodes.Name+$standaloneFileServers

# function to obtain the version of a particular excecutable
function getExecutableVersion{
param(
[string]$computername,
[string]$executablename="robocopy.exe"
)
$localComputerName=$env:computername
$isLocal=if($computername -match "(localhost|127.0.0.1|$localComputerName)"){$true}else{$false}

function getExeInfo{
param($exeName)
$exeFile=$(try{get-command $exeName -ea SilentlyContinue}catch{}).Definition;
$exeInfo=$(try{get-item $exeFile -ea SilentlyContinue}catch{}).versionInfo;
$osName=(Get-WmiObject -class Win32_OperatingSystem).Caption;
$exeInfo | Add-Member -MemberType NoteProperty -Name "osName" -Value $osName
return $exeInfo;
}

if ($isLocal){
$exeInfo=getExeInfo -exeName $executablename;
}else{
$exeInfo=invoke-command -computername $computername -scriptblock{
param($importedFunc,$executablename)
[ScriptBlock]::Create($importedFunc).Invoke($executablename);
} -args ${function:getExeInfo},$executablename
}
if ($exeInfo){
$exeVersion=$exeInfo.ProductVersion;
$exeLocation=$exeInfo.FileName;
$fileVersion=$exeInfo.FileVersion;
$osName=$exeInfo.osName;
return "$computername $osName $executablename $exeVersion$(if($exeVersion -ne $fileVersion){" $fileVersion "})$exeLocation";
}else{
write-host "$executablename is not in the environmental paths of $computername";
}
}

$computerNames|getExecutableVersion -computerName $_ -executablename robocopy.exe

PowerShell: Add System Backup Privileges

function addSystemPrivilege{
param(
[String[]]$privileges=@("SeBackupPrivilege","SeRestorePrivilege")
)

function includeSystemPrivileges{
$win32api = @'
using System;
using System.Runtime.InteropServices;

namespace SystemPrivilege.Win32API
{
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public UInt32 LowPart;
public Int32 HighPart;
}

[StructLayout(LayoutKind.Sequential)]
public struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
public UInt32 Attributes;
}

[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_PRIVILEGES
{
public UInt32 PrivilegeCount;
public LUID Luid;
public UInt32 Attributes;
}

public class Privileges
{
public const UInt32 DELETE = 0x00010000;
public const UInt32 READ_CONTROL = 0x00020000;
public const UInt32 WRITE_DAC = 0x00040000;
public const UInt32 WRITE_OWNER = 0x00080000;
public const UInt32 SYNCHRONIZE = 0x00100000;
public const UInt32 STANDARD_RIGHTS_ALL = (
READ_CONTROL |
WRITE_OWNER |
WRITE_DAC |
DELETE |
SYNCHRONIZE
);
public const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000u;
public const UInt32 STANDARD_RIGHTS_READ = 0x00020000u;

public const UInt32 SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001u;
public const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002u;
public const UInt32 SE_PRIVILEGE_REMOVED = 0x00000004u;
public const UInt32 SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000u;

public const UInt32 TOKEN_QUERY = 0x00000008;
public const UInt32 TOKEN_ADJUST_PRIVILEGES = 0x00000020;

public const UInt32 TOKEN_ASSIGN_PRIMARY = 0x00000001u;
public const UInt32 TOKEN_DUPLICATE = 0x00000002u;
public const UInt32 TOKEN_IMPERSONATE = 0x00000004u;
public const UInt32 TOKEN_QUERY_SOURCE = 0x00000010u;
public const UInt32 TOKEN_ADJUST_GROUPS = 0x00000040u;
public const UInt32 TOKEN_ADJUST_DEFAULT = 0x00000080u;
public const UInt32 TOKEN_ADJUST_SESSIONID = 0x00000100u;
public const UInt32 TOKEN_READ = (
STANDARD_RIGHTS_READ |
TOKEN_QUERY
);
public const UInt32 TOKEN_ALL_ACCESS = (
STANDARD_RIGHTS_REQUIRED |
TOKEN_ASSIGN_PRIMARY |
TOKEN_DUPLICATE |
TOKEN_IMPERSONATE |
TOKEN_QUERY |
TOKEN_QUERY_SOURCE |
TOKEN_ADJUST_PRIVILEGES |
TOKEN_ADJUST_GROUPS |
TOKEN_ADJUST_DEFAULT |
TOKEN_ADJUST_SESSIONID
);

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
public static extern IntPtr GetCurrentProcess();

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
public static extern IntPtr GetCurrentThread();

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID lpLuid);

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, UInt32 BufferLengthInBytes, IntPtr PreviousStateNull, IntPtr ReturnLengthInBytesNull);

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle);

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool OpenThreadToken(IntPtr ThreadHandle, UInt32 DesiredAccess, bool OpenAsSelf, out IntPtr TokenHandle);

[DllImport("ntdll.dll", EntryPoint = "RtlAdjustPrivilege")]
public static extern int RtlAdjustPrivilege(
UInt32 Privilege,
bool Enable,
bool CurrentThread,
ref bool Enabled
);

[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr handle);

//
//

private static LUID LookupPrivilege(string privilegeName)
{
LUID privilegeValue = new LUID();

bool res = LookupPrivilegeValue(null, privilegeName, out privilegeValue);

if (!res)
{
throw new Exception("Error: LookupPrivilegeValue()");
}

return privilegeValue;
}

//
//

public static void AdjustPrivilege(string privilegeName, bool enable)
{
IntPtr accessToken = IntPtr.Zero;
bool res = false;

try
{
LUID privilegeValue = LookupPrivilege(privilegeName);

res = OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, false, out accessToken);

if (!res)
{
res = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out accessToken);

if (!res)
{
throw new Exception("Error: OpenProcessToken()");
}
}

TOKEN_PRIVILEGES tokenPrivileges = new TOKEN_PRIVILEGES();
tokenPrivileges.PrivilegeCount = 1;
tokenPrivileges.Luid = privilegeValue;

if (enable)
{
tokenPrivileges.Attributes = SE_PRIVILEGE_ENABLED;
}
else
{
tokenPrivileges.Attributes = 0;
}

res = AdjustTokenPrivileges(accessToken, false, ref tokenPrivileges, (uint)System.Runtime.InteropServices.Marshal.SizeOf(tokenPrivileges), IntPtr.Zero, IntPtr.Zero);

if (!res)
{
throw new Exception("Error: AdjustTokenPrivileges()");
}
}

finally
{
if (accessToken != IntPtr.Zero)
{
CloseHandle(accessToken);
accessToken = IntPtr.Zero;
}
}
}
}
}
'@

if ([object]::Equals(('SystemPrivilege.Win32API.Privileges' -as [type]), $null)) {
Add-Type -TypeDefinition $win32api
}
}
includeSystemPrivileges;


$privileges|%{[SystemPrivilege.Win32API.Privileges]::AdjustPrivilege($_, $true)}

# Validation
whoami /priv|?{$_ -match "SeBackupPrivilege|SeRestorePrivilege"}
}
addSystemPrivilege;

PowerShell: Deleting a Single File Safely

# Name your file
$item="\\FILESHERVER007\someweird folder names with long paths\ fmmkklghhbb-yj-yuyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy p-hphlfiles\whatup.exe - Shortcut.lnk" #item could be file or folder

# Move such item into a temp directory, then purge that temp folder
$tempDirectory="c:\tempDirectory"
$emptyDirectory="c:\emptyDirectory"
mkdir $emptyDirectory | out-null
mkdir $emptyDirectory | out-null
$filename=split-path $item -Leaf
$foldername=split-path $item -Parent
robocopy $foldername $tempDirectory $filename /MOVE
robocopy $emptyDirectory $tempDirectory /purge

Active Directory GPO Practical Examples

Fonts Distribution
———————————
A. Create an SMB share on an Intranet accessible directory \\SOFTWARE\FONTS\Kim-Connect.ttf
B. Create a new GPO named “Fonts-Distribution”
– Scope: Authenticated Users (which includes Domain Users and Domain Computers)
– Links: Computers OU, and any other OU to distribute these fonts
C. Edit the GPO by following these procedures:

Right-click the “Fonts-Distribution” GPO > Edit > Navigate to User Configuration > Preferences > Windows Settings > right-click Files > New > File > set Source file(s) = \\SOFTWARE\FONTS\Kim-Connect.ttf > set Destination File = C:\Windows\Fonts\Kim-Connect.ttf > Click OK

Navigate to User Configuration > Preferences > Windows Settings > right-click Registry > New > set these values
– Hive: HKEY_LOCAL_MACHINE
– Key: SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts
– Value Name: Kim Connect
– Value type: REG_SZ
– Value data: Kim-Connect.ttf
– Click OK when done

Computer Access Policy – Logon Banner
———————————
Create a GPO named “Computer Access Policy – Logon Banner” > Apply it to the appropriate OUs > Edit GPO > navigate to Computer Configuration > Windows Settings > Security Settings > Local Policies > Security Options > Interactive Logon > Edit these 2 Settings:
– Interactive logon: Message text for users attempting to log on = This computer is a property of Kim Connect, LLC. The use of this system is restricted to authorized personnel only. All activities on these systems are subject to monitoring and security audits. Be advised that this warning is not an invitation for any unapproved pen testing. Unauthorized access, use, or modification of this computer system or the data contained herein or in transit to/from this system may subject you to criminal prosecution. Employees who act contrary to company policy are subject to disciplinary actions, including termination. Click OK to acknowledge that you have read and understand the above terms and conditions.
– Interactive logon: Message title for users attempting to log on = Computer Access Policy

Computer Configuration > Policies > Administrative Templates > System > Group Policy > double-click Configure User Group Policy loopback processing mode > Select Enabled > select a loopback processing mode = Merge > OK

Enable RDP
———————————
Computer Configuration > Policies > Administrative Templates > Windows Components > Remote Desktop Services > Remote Desktop Session Hosts > Connections > Allow users to connect remotely by using Remote Desktop Services = Enabled > Remote Session Environment > Enforce Removal of Remote Desktop Wallpaper = Enabled

PowerShell: Increase Default Windows 260-Character Paths Limit

# This function increases the default windows 260 characters path length limit to 1024
Function remove260CharsPathLimit{
param(
[string]$computerName=$env:computername
)
# Declare static variables
$registryHive= "SYSTEM\CurrentControlSet\Control\FileSystem"
$keyName = "LongPathsEnabled"
$value= 1

# The legacy command-line method
#$result=REG ADD "\\$computerName\HKLM\$registryHive" /v $keyName /t REG_DWORD /d $value /f
#if($result){"\\$computerName\HKLM\$registryHive\$keyName has been added successfully."}

#The PowerShell Method to set remote registry key
$registryHive="SYSTEM\CurrentControlSet\Control\FileSystem"
$keyName="LongPathsEnabled"
$value=1
$remoteRegistry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine",$computerName)
$remoteHive = $remoteRegistry.OpenSubKey($registryHive,$True)
$remoteHive.CreateSubKey($keyName)|out-null
$remoteKeyValue = $remoteRegistry.OpenSubKey("$registryHive\$keyName",$True)
$remoteKeyValue.SetValue($keyName,$value)

# Validation
$resultKey=$remoteRegistry.OpenSubKey("$registryHive\$keyName")
if($resultKey.GetValue($keyName) -eq $value){"\\$computerName\HKLM\$registryHive\$keyName has been added successfully."}
}

# This reverses the effects of that previous function
Function restore260CharsPathLimit{
param([string]$computerName=$env:computername)

$registryHive= "SYSTEM\CurrentControlSet\Control\FileSystem"
$keyName = "LongPathsEnabled"
$result=REG DELETE "\\$computerName\HKLM\$registryHive" /v $keyName /f
if($result){"\\$computerName\HKLM\$registryHive\$keyName has been removed successfully."}
}

PowerShell: Windows Systems State Backup Daily

# Windows-Systems-State-Backup-V0.1.ps1
#
# What this script does:
# 1. Create a Windows Scheduled task on an Active Directory controller to perform daily backups at a predetermined time
# 2. Output a log to include backup duration and result of last backup
# 3. Remove backup files that are outside of the retention period
#
# Requirements:
# Powershell 3.0 is necessary to avoid this error, "the term 'Register-ScheduledJob' is not recognized as the name of a cmdlet"

# Set variables:
$taskName="System State Backup";
$storageUncPath="\\Backups\ActiveDirectory\";
$frequency="Daily";
$executionTime="2:00AM";
$retentionDays=7;
$pdcServer=(Get-ADForest |Select-Object -ExpandProperty RootDomain |Get-ADDomain |Select-Object -Property PDCEmulator).PDCEmulator;

# Optional: obtain a domain admin service account credentials
$prompt = "Username and Password of Domain Admin"
$cred = $Host.UI.PromptForCredential("Credential",$prompt,"$env:userdomain\$env:username",$env:userdomain)

# Ensure that uncPath has a trailing backslash
if ($storageUncPath[$storageUncPath.length-1] -ne "\"){$storageUncPath+="\";}

Function createBackupScheduledTask{
param(
$taskName="System State Backup",
$storageUncPath,
$frequency="Daily",
$time="2:00AM",
$retentionDays=7,
$cred=$credential
)

Function removeBackupScheduledTask{
param(
$taskName="System State Backup"
)
Unregister-ScheduledTask $taskname -confirm:$false -ErrorAction SilentlyContinue
Write-Host "Taskname $taskName is deleted."
$checkJob=Get-ScheduledJob $taskname -ErrorAction SilentlyContinue #This command updates the metadata related to deleted task to fully void its remnants
}

$backupScheduledTask=Get-ScheduledTask $taskName -ErrorAction SilentlyContinue
if ($backupScheduledTask){write-host "$taskName already exists. Program will now remove the previous task instance.";removeBackupScheduledTask;}

Do {
Try{
$taskCreated=Register-ScheduledJob -ErrorAction Stop -Name $taskName -ScriptBlock{
param($storageUncPath,$retentionDays)

# Start a timer
$timer=[System.Diagnostics.Stopwatch]::StartNew();

# Setup backup storage location
$backupDirectory=$storageUncPath;
$dateStamp=Get-Date -Format yyyy-MM-dd;
$logFile=$backupDirectory+"backup-log.txt";
$dateStampedFolder=$backupDirectory+$dateStamp;
New-Item -ItemType Directory $dateStampedFolder -Force;
$backupStorageLocation = New-WBBackupTarget -Network $dateStampedFolder;

# Instantiate a new policy
$thisPolicy = New-WBPolicy;

#Add System State to the policy
Add-WBSystemState -Policy $thisPolicy;

#Add backup location to policy
Add-WBBackupTarget -Policy $thisPolicy -Target $backupStorageLocation;

#Start backup using policy
Start-WBBackup -Policy $thisPolicy;

# Cleanup folders that are outside of retention period
$thisComputer=$env:computername;
if($backupDirectory -ne $null){
$expiredFolders=(get-childitem $backupDirectory|?{ $_.PSIsContainer }|?{$_.LastWriteTime -lt (get-date).AddDays(-$retentionDays)}|Select FullName).FullName;
}else{
$expiredFolders=$null;
}
if($expiredFolders){
$removedBackupSets = $expiredFolders|%{Remove-WBBackupSet -NetworkPath $_ -ComputerName $thisComputer -Force}
if($removedBackupSets){$expiredFolders|%{for ($i=0;$i -lt 2;$i++){try{remove-item -LiteralPath $_ -Force -Recurse}catch{}}};} #First pass, delete children; second pass, delete parent
}

#Append result into a log
#$lastBackupTime=(Get-WinEvent -LogName Microsoft-Windows-Backup -FilterXPath "*[System[EventID=4]]"|select -First 1).TimeCreated;
$lastBackupTime=(Get-WBBackupSet|select -last 1).BackupTime;
$hours=[math]::Round(($timer.Elapsed.TotalSeconds)/3600,2);
Add-Content $logFile "`r`n$lastBackupTime` --- $thisComputer backup completed in $hours hours.`r`n$removedBackupSets";
} -ScheduledJobOption $(New-ScheduledJobOption -RunElevated) -Trigger @{Frequency = $frequency; At=$time} -ArgumentList $storageUncPath,$retentionDays -Credential $cred;
if ($taskCreated){write-host "$taskName to run $frequency at $time has been successfully created.";};
}
catch{
$taskCreated=$false;
write-host "Waiting 10 seconds for next retry...";
$checkJob=Get-ScheduledJob $taskname -ErrorAction SilentlyContinue;
sleep 10;
continue;
}
}while($taskCreated -eq $false)
}

#createBackupScheduledTask -taskName $taskName -storageUncPath $storageUncPath -frequency $frequency -time $executionTime -retention $retentionDays

if($pdcServer){
invoke-command -Credential $cred -computername $pdcServer -scriptBlock{
param($importedFunc,$variable1,$variable2,$variable3,$variable4,$variable5)
$credential=$using:cred
[ScriptBlock]::Create($importedFunc).invoke($variable1,$variable2,$variable3,$variable4,$variable5,$credential);
} -Args ${function:createBackupScheduledTask},$taskName,$storageUncPath,$frequency,$executionTime,$retentionDays
}else{write-host "set pdcServer variable before proceeding"}

 

Sample execution result:

System State Backup already exists. Program will now remove the previous task instance.
Taskname System State Backup is deleted.
System State Backup to run Daily at 2:00AM has been successfully created.

PowerShell: How to Properly Delete a Msol User Account

# Set the user ObjectID attribute variable
$msolUser="6f2fcfcd-...."

# Move user account to Recycle Bin
Remove-MsolUser -ObjectId $msolUser

# Purge from Recycle Bin
Get-MsolUser -ObjectID $msolUser -ReturnDeletedUsers | Remove-MsolUser -RemoveFromRecycleBin

# Remove from contacts bin
Get-MsolContact -ObjectID $msolUser | FL #Validate that this is a correct account
Get-MsolContact -ObjectID $msolUser | Remove-MsolContact

# Optional: if object is a group instead of a user
Get-MsolGroup -ObjectID $msolUser | Select EmailAdress,ProxyAddresses #Validate that this is the correct account
Get-MsolGroup -ObjectID $msolUser | Remove-MsolGroup

PowerShell: Play with Time

1. Constructor:

$timer=[System.Diagnostics.Stopwatch]::StartNew()

2. Accessing a property

$totalSeconds=$timer.Elapsed.TotalSeconds

3. Output Time (Usage):

$hours=[math]::round($totalSeconds/3600,2)
write-host "It has been $hours hours."

4. Destructor (optional as the system automatically performs this task when session goes out of scope):

$timer.stop()

PowerShell: Accessing the Reflection Assembly Class to Retrieve User Context

Step 1: Accessing Reflection Assembly namespace to call method Load with Windows Account Management as the input object

PS C:\Windows\system32> [reflection.assembly]::LoadWithPartialName("System.DirectoryServices.AccountManagement")

GAC Version Location
--- ------- --------
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.DirectoryServices.AccountManagement\v4.0_4.0...

Step 2: Access the UserPrinciple property to return Current user

PS C:\Windows\system32> [System.DirectoryServices.AccountManagement.UserPrincipal]::Current


GivenName :
MiddleName :
Surname :
EmailAddress :
VoiceTelephoneNumber :
EmployeeId :
AdvancedSearchFilter : System.DirectoryServices.AccountManagement.AdvancedFilters
Enabled : True
AccountLockoutTime :
LastLogon : 10/27/2019 9:16:26 PM
PermittedWorkstations : {}
PermittedLogonTimes : {255, 255, 255, 255...}
AccountExpirationDate :
SmartcardLogonRequired : False
DelegationPermitted : True
BadLogonCount : 0
HomeDirectory :
HomeDrive :
ScriptPath :
LastPasswordSet : 3/15/2019 4:37:35 AM
LastBadPasswordAttempt :
PasswordNotRequired : True
PasswordNeverExpires : True
UserCannotChangePassword : False
AllowReversiblePasswordEncryption : False
Certificates : {}
Context : System.DirectoryServices.AccountManagement.PrincipalContext
ContextType : Machine
Description :
DisplayName :
SamAccountName : baloo
UserPrincipalName :
Sid : S-1-5-21-3577067864-56025819-3891708608-1002
Guid :
DistinguishedName :
StructuralObjectClass :
Name : baloo

Step 3: View the available methods of current user context

PS C:\Windows\system32> [System.DirectoryServices.AccountManagement.UserPrincipal]::Current | Get-Member


TypeName: System.DirectoryServices.AccountManagement.UserPrincipal

Name MemberType Definition
---- ---------- ----------
ChangePassword Method void ChangePassword(string oldPassword, string newPassword)
Delete Method void Delete()
Dispose Method void Dispose(), void IDisposable.Dispose()
Equals Method bool Equals(System.Object o)
ExpirePasswordNow Method void ExpirePasswordNow()
GetAuthorizationGroups Method System.DirectoryServices.AccountManagement.PrincipalSearchResult[System...
GetGroups Method System.DirectoryServices.AccountManagement.PrincipalSearchResult[System...
GetHashCode Method int GetHashCode()
GetType Method type GetType()
GetUnderlyingObject Method System.Object GetUnderlyingObject()
GetUnderlyingObjectType Method type GetUnderlyingObjectType()
IsAccountLockedOut Method bool IsAccountLockedOut()
IsMemberOf Method bool IsMemberOf(System.DirectoryServices.AccountManagement.GroupPrincip...
RefreshExpiredPassword Method void RefreshExpiredPassword()
Save Method void Save(), void Save(System.DirectoryServices.AccountManagement.Princ...
SetPassword Method void SetPassword(string newPassword)
ToString Method string ToString()
UnlockAccount Method void UnlockAccount()
AccountExpirationDate Property System.Nullable[datetime] AccountExpirationDate {get;set;}
AccountLockoutTime Property System.Nullable[datetime] AccountLockoutTime {get;}
AdvancedSearchFilter Property System.DirectoryServices.AccountManagement.AdvancedFilters AdvancedSear...
AllowReversiblePasswordEncryption Property bool AllowReversiblePasswordEncryption {get;set;}
BadLogonCount Property int BadLogonCount {get;}
Certificates Property System.Security.Cryptography.X509Certificates.X509Certificate2Collectio...
Context Property System.DirectoryServices.AccountManagement.PrincipalContext Context {get;}
ContextType Property System.DirectoryServices.AccountManagement.ContextType ContextType {get;}
DelegationPermitted Property bool DelegationPermitted {get;set;}
Description Property string Description {get;set;}
DisplayName Property string DisplayName {get;set;}
DistinguishedName Property string DistinguishedName {get;}
EmailAddress Property string EmailAddress {get;set;}
EmployeeId Property string EmployeeId {get;set;}
Enabled Property System.Nullable[bool] Enabled {get;set;}
GivenName Property string GivenName {get;set;}
Guid Property System.Nullable[guid] Guid {get;}
HomeDirectory Property string HomeDirectory {get;set;}
HomeDrive Property string HomeDrive {get;set;}
LastBadPasswordAttempt Property System.Nullable[datetime] LastBadPasswordAttempt {get;}
LastLogon Property System.Nullable[datetime] LastLogon {get;}
LastPasswordSet Property System.Nullable[datetime] LastPasswordSet {get;}
MiddleName Property string MiddleName {get;set;}
Name Property string Name {get;set;}
PasswordNeverExpires Property bool PasswordNeverExpires {get;set;}
PasswordNotRequired Property bool PasswordNotRequired {get;set;}
PermittedLogonTimes Property byte[] PermittedLogonTimes {get;set;}
PermittedWorkstations Property System.DirectoryServices.AccountManagement.PrincipalValueCollection[str...
SamAccountName Property string SamAccountName {get;set;}
ScriptPath Property string ScriptPath {get;set;}
Sid Property System.Security.Principal.SecurityIdentifier Sid {get;}
SmartcardLogonRequired Property bool SmartcardLogonRequired {get;set;}
StructuralObjectClass Property string StructuralObjectClass {get;}
Surname Property string Surname {get;set;}
UserCannotChangePassword Property bool UserCannotChangePassword {get;set;}
UserPrincipalName Property string UserPrincipalName {get;set;}
VoiceTelephoneNumber Property string VoiceTelephoneNumber {get;set;}

PowerShell: Detect Antivirus Name on a Windows Machine

function getAntivirusName {  
$wmiQuery = "SELECT * FROM AntiVirusProduct"
$antivirus = Get-WmiObject -Namespace "root\SecurityCenter2" -Query $wmiQuery @psboundparameters -ErrorVariable myError -ErrorAction 'SilentlyContinue'

if($antivirus){
return $antivirus.displayName
}else{
$alternateAntivirusQuery=WMIC /Node:localhost /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct GET displayName /Format:List|?{$_.trim() -ne ""}|%{$_ -replace "displayName=",""}
if ($alternateAntivirusQuery){
return $alternateAntivirusQuery
}else{
write-host "No antivirus software were detected. Hence, namespace querying errors."
$rawSearch=((get-wmiobject -class "Win32_Process" -namespace "root\cimv2" | where-object {$_.Name.ToLower() -match "antivirus|endpoint|protection|security|defender|msmpeng"}).Name | Out-String).Trim();
if($rawSearch){
return $rawSearch
}else{
return "No antivirus detected."
}
}

}
}
getAntivirusName;

PowerShell: How To Add Additional Sub-Directories to an Existing SMB Share

Business Use-Case:

  1. There’s an existing logon script or Group Policy that maps users toward a particular share on a file server (e.g. “NET USE P:\ \\FILESHERVER01\Public /USER:INTRANET.KIMCONNECT.COM\%USERNAME%”)
  2. The requirement is to add more directories to that existing share without incurring extra storage on existing volume where that share resides
  3. There is a new LUN being dedicated to that file server that is intended to be mounted as a sub-directory at path \\FILESHERVER01\Public\Infrastructure
  4. There are other existing volumes at local paths of T:\Software and M:\Public_Relations, and all other existing shares on this server, that must be accessible from \\FILESHERVER01\Public as sub-folders named Software and Public_Relations, etc.

Technical Implementation:

  • a. There shall be no changes required for the logon script. Hence, no change requests are needed to perform this task.
  • b. The default recommendation is to expand the volume the hosting share \\FILESHERVER01\Public. If that is not feasible, then items (c) and (d) shall apply
  • c. These are the PowerShell commands to mount a new volume as a folder inside \\FILESHERVER01\Public
<# Method 1: Dedicate a whole volume as a sub-directory 
Warning: all data on this new volume will be erased.

#### Set variables ####
$mountPath=""S:\Public\Infrastructure"
$diskIndex=5
#### Only edit above lines ####

$disk = Get-Disk $diskIndex
$disk | Clear-Disk -RemoveData -Confirm:$false
$disk | Initialize-Disk -PartitionStyle MBR
$disk | New-Partition -UseMaximumSize -MbrType IFS
$partition = Get-Partition -DiskNumber $disk.Number
$partition | Format-Volume -FileSystem NTFS -Confirm:$false
New-Item -ItemType Directory -Force -Path $mountPath
$partition | Add-PartitionAccessPath -AccessPath $mountPath

#>
  • d. This PowerShell snippet shall retrieve existing SMB shares on the file server and create “junctions” that are accessible from \\FILESHERVER01\Public
<# Method 2: Create Junction Points to All Shares On File Server 

#### Set variables ############
$shareRoot="S:\Public"
#### Only edit above lines ####

# Gather SMB shares on this file server
$paths=get-smbshare|?{$_.Name -notlike "*$"}|select Name,Path

# Install junction.exe
if (!(get-command junction)){
if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
}
choco install junction -y
}

# Create junctions at SMB Share Root with a given path
function createJunction{
param(
[string]$path,
[string]$smbShareRoot=$shareRoot
)
$folderName=Split-Path $path -Leaf
#Write-Host "junction '$smbShareRoot\$folderName' '$path'"
invoke-expression "junction '$smbShareRoot\$folderName' '$path'"
}

foreach ($path in $paths){
if ($path.Path -notlike "$shareRoot*"){
Write-Host "Creating juncion for $($path.Path)";
createJunction -path $path.Path -smbShareRoot $shareRoot;
}
}

PowerShell: Benchmark Disk Speed

function getDiskSpeed{
<#
Purpose: to measure IOPS of local volumes or UNC paths
- This function must be ran with Administrator privileges, especially when triggered for the first time on a local system
- Two utilities will be required and installed on the system: chocolatey (DevOps Tool) and diskspd.exe (Microsoft's disk benchmark app, similar to SQLIO)
- Call the function with a valid path to test disk speed, such as:
getDiskSpeed C:\
getDiskSpeed \\FILESHERVER01\TESTDIR1
getDiskSpeed D
#>
param(
[string]$driveLetter="c:",
[int]$sampleSize=5
)
if($driveLetter.Length -eq 1){$driveLetter+=":";}

function isPathWritable{
param($testPath)
# Create random test file name
$tempFolder=$testPath+"\getDiskSpeed\"
$filename = "diskSpeedTest-"+[guid]::NewGuid()
$tempFilename = (Join-Path $tempFolder $filename)
New-Item -ItemType Directory -Path $tempFolder -Force -EA SilentlyContinue|Out-Null

Try {
# Try to add a new file
# New-Item -ItemType Directory -Path $tempFolder -Force -EA SilentlyContinue
[io.file]::OpenWrite($tempFilename).close()
#Write-Host -ForegroundColor Green "$testPath is writable."

# Delete test file after done
# Remove-Item $tempFilename -Force -ErrorAction SilentlyContinue

# Set return value
$feasible=$true;
}
Catch {
# Return 'false' if there are errors
$feasible=$false;
}

return $feasible;
}

# Check if input is a valid drive letter
function validatePath{
param([string]$path=$driveLetter)
if (Test-Path $path -EA SilentlyContinue){
$regexValidDriveLetters="^[A-Za-z]\:{0,1}$"
$validLocalPath=$path.SubString(0,2) -match $regexValidDriveLetters
if ($validLocalPath){
write-Host "Local directory detected."
$driveLettersOnThisComputer=ls function:[A-Z]: -n|?{test-path $_}
if (!($driveLettersOnThisComputer -contains $path.SubString(0,2))){
Write-Host "The provided local path's first 2 characters do not match any volumes in this system.";
return $false;
}
return $(isPathWritable $path)
}else{
$regexUncPath="^\\(?:\\[^<>:`"/\\|?*]+)+$"
if ($path -match $regexUncPath){
write-Host "UNC directory detected."
return $(isPathWritable $path)
}else{Write-Host "The provided path does not match a UNC pattern nor a local drive.";return $false;}
}
}else{
Write-Host "The path $path currently does NOT exist";
Return $false;
}
}

if (validatePath){

# Set variables
$tempDirectory="$driveLetter`\getDiskSpeed"
# New-Item -ItemType Directory -Force -Path $tempDirectory|Out-Null
$testFile="$tempDirectory`\testfile.dat"
$processors=(Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors

# Ensure that diskspd.exe is available in the system
$diskSpeedUtilityAvailable=get-command diskspd.exe -ea SilentlyContinue
if (!($diskSpeedUtilityAvailable)){
if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
}
choco install diskspd -y;
refreshenv;
}

function getIops{
# Sometimes, the test result throws this error "diskspd Error opening file:" if no switches were used
# The work around is to specify more parameters
# Other variations:
# $testResult=diskspd.exe-d1 -o4 -t4 -b8k -r -L -w50 -c1G $testFile
# $testResult=diskspd.exe -b4K -t1 -r -w50 -o32 -d10 -c8192 $testFile
# Note: remove the -c option to avoid this error when running with unprivileged accounts
# diskspd.exe : WARNING: Error adjusting token privileges for SeManageVolumePrivilege (error code: 1300)

try{
$testResult=invoke-expression "diskspd.exe -b8k -d1 -o$processors -t$processors -r -L -w25 -c1G $testfile";
<# diskspd.exe -b8k -d1 -o4 -t4 -r -L -w25 -c1G $testfile
8K block size; 1 second random I/O test;4 threads; 4 outstanding I/O operations;
25% write (implicitly makes read 75% ratio);
#>
}
catch{
$errorMessage = $_.Exception.Message
$failedItem = $_.Exception.ItemName
Write-Host "$errorMessage $failedItem";
continue;
}
$x=$testResult|select-string -Pattern "total*" -CaseSensitive|select-object -First 1|out-String
$iops=$x.split("|")[-3].Trim()
#$mebibytesPerSecond=$x.split("|")[-4].Trim()
return $iops
}

function selectHighIops{
$testArray=@();
for($i=1;$i -le $sampleSize;$i++){
try{
$testArray+=getIops;
}
catch{
$errorMessage = $_.Exception.Message
$failedItem = $_.Exception.ItemName
Write-Host "$errorMessage $failedItem";
break;
}
}
$highestResult=($testArray|measure -Maximum).Maximum
return $highestResult
}

# Trigger several tests and select the highest value
$selectedIops=selectHighIops

# Cleanup
# cmd /c rd $tempDirectory
function isFileLocked{
param($file=$(New-Object System.IO.FileInfo $testFile))
if (Test-Path $testFile){
try {
$fileHandle = $file.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
if ($fileHandle){
# File handle is open, which means file is not locked
$fileHandle.Close()
}
return $false
}
catch{
# file is locked
return $true
}
}else{return $false}
}

do {
sleep 1;
isFileLocked|out-null;
}until(!(isFileLocked))
Remove-Item -Recurse -Force $tempDirectory

$mebibytesPerSecond=[math]::round($(([int]$selectedIops)/128),2)
return "$mebibytesPerSecond MiB/s ($selectedIops IOPS)";
}else{return "Cannot get disk speed"}
}
getDiskSpeed;

Bash Shell: Old School Migration of ESXi Guest Virtual Machines

Step 0: Preparations

Estimate file-copying duration for cut-over:
– Sustained storage transfer speed using a 1GB Ethernet Connection is approximately 99.12 MB/s (Mebibytes per second)
– To move 1GB (1024MB) of storage, it takes 10.33 seconds
– 100GB will require 1033 seconds (17.22 minutes)

Perform some systems discovery on ESXi hosts:

# The quick method of listing available volumes
mountDirectory=/vmfs/volumes
availableVolumes=$(echo "ls $mountDirectory"|readlink -f $_)

# Alternative and more convoluted method of listing available volumes
mountDirectory=/vmfs/volumes
symlinkMarker="\->"
symlinks=$(ls -la $mountDirectory/ | grep $symlinkMarker)
availableVolumes=$(echo $symlinks | eval "sed -n -e 's/^.*$symlinkMarker //p'"|echo $mountDirectory/$_)
# Explain:
# - Pipe $symlinks to eval to execute an expression. Similar to Invoke-Command in Powow Shill
# - Sed is the go-to tool for search and replace
# - -n to default as not to print anything
# - -e follows the sed command as default syntax. It probably stands for evaluate
# - s is to search and replace
# - /^.* means all characters from beginning of string toward delimiter of $symlinkMarker as the $matches
# - // means empty string as replacement for the $matches
# - p is to print the result
# - Last section is to append $mountDirectory to the sub-directory names

# List mounts
echo $availableVolumes

# List contents of each mount. Please note that explicit casting of variables with double quotes is a
# requirement for pipe operations, allowing processing of such variables line-by-line, rendering $_ variable as per line
echo "$availableVolumes" | ls $_

Define these variables:

# Define source variables
SOURCESERVER=10.10.10.101
SOURCESERVERPORT=22
SOURCEUSERNAME=brucelee
sourceStoragePath=/vmfs/volumes/5ed459d2-8f5430cd-3762-94c691ac4caa
sourceDirectoryName=Windows-PC1
SOURCEDIRECTORY=$sourceStoragePath/$sourceDirectoryName/
SOURCEFILES=$SOURCEDIRECTORY*.*

# Define destination variables
DESTINATIONSERVER=10.10.10.100
DESTINATIONSERVERPORT=22
DESINATIONUSERNAME=brucelee
destinationStoragePath=/vmfs/volumes/5c8206f0-49fe1606-76b6-94c691abb6e2
destinationDirectoryName=Windows-PC1
DESTINATIONDIRECTORY=$destinationStoragePath/$destinationDirectoryName/

vmxName=$destinationDirectoryName.vmx
vmdkName=$destinationDirectoryName.vmdk
VMXFILE=$DESTINATIONDIRECTORY$vmxName
VMDKFILE=$DESTINATIONDIRECTORY$vmdkName

resizedVmdkName=$destinationDirectoryName-resized.vmdk
resizedVmdk=$DESTINATIONDIRECTORY$resizedVmdkName

Remove any localized resources on the reference VM that the destination server does not have access, such as:
– Virtual disks
– Virtual networks
– ISO mounts
– USB pass-through dongles

Step 1: Ensure that SSH Server is running at Destination and SSH Client is running at Source

SSH Server Settings:
Host > Manage > Services > right-click the SSH service with alias “TSM-SSH” > Start SSH service (if not already started)

SSH Client Settings:
Networking > Firewall rules > right-click SSH Client > “Enable” (if not already enabled)

Step 2: Sanitize the destination
# ssh into Destination server
ssh $DESINATIONUSERNAME@$DESTINATIONSERVER$DESTINATIONSERVERPORT
# Optional: create the destination directory if not exists
mkdir -p $destinationStoragePath/$destinationDirectoryName
# Optional: Empty destination directory
rm -r $DESTINATIONDIRECTORY*
Step 3: Check SSH Services while login to a SSH session of the Source Server
# ssh into Source server
ssh $SOURCEUSERNAME@$SOURCESERVER -p$SOURCESERVERPORT
# Verify Destination SSH reachability
ssh $DESINATIONUSERNAME@$DESTINATIONSERVER -p$DESTINATIONSERVERPORT "echo 2>&1" && echo $DESTINATIONSERVER OK || echo $DESTINATIONSERVER NOT-OK
Step 4: Perform the copy
# ssh into Source server
ssh $SOURCEUSERNAME@$SOURCESERVER -p$SOURCESERVERPORT
# Use SCP to copy files
scp -P $DESTINATIONSERVERPORT $SOURCEFILES $DESINATIONUSERNAME@$DESTINATIONSERVER:$DESTINATIONDIRECTORY
Step 5: Register the Migrated VM on New Host
# ssh into Destination server
ssh $DESINATIONUSERNAME@$DESTINATIONSERVER$DESTINATIONSERVERPORT
# Register VM with this host
vmid=$(vim-cmd solo/registervm $VMXFILE)

$ Power On VM
# List all VMs: vim-cmd /vmsvc/getallvms
# Check VM power state: vim-cmd /vmsvc/power.getstate $vmid
vim-cmd vmsvc/power.on $vmid

# If this machine is freshly registered on this host, it will require a confirmation that it has been "copied" or "moved" via the ESXi GUI. In such context, migrated correlates to moved.

Sample Output:

[brucelee@esx02:~] echo $VMXFILE
/vmfs/volumes/5ed456fa-5daa2d25-e3bd-94c691ac4caa/Windows-PC1/Windows-PC1.vmx
[brucelee@esx02:~] vim-cmd solo/registervm $VMXFILE
3

Optional: resize the main disk and attach the resized

# Convert from thick to thin provisioning by cloning the original disk to a thin-provisioned copy
vmkfstools -i $VMDKFILE -d thin $resizedVmdk

# Reset the resizing virtual disk to a $desiredDiskSize:
desiredDiskSize=120g
vmkfstools -X $desiredDiskSize $resizedVmdk

# Detach the original disk
# Usage: device.diskremove vmid 'controller number' 'unit number' 'delete file'(y/n)
vim-cmd vmsvc/device.diskremove $vmid 0 0 n

# Attach resized disk to scsi controller zero disk index 0 (or any available index)
vim-cmd vmsvc/device.diskaddexisting $vmid $resizedVmdk 0 0
Step 6: Unregister VM from Old Host
# ssh into Source server
ssh $SOURCEUSERNAME@$SOURCESERVER -p$SOURCESERVERPORT
# Check all VMs
[brucelee@esx02:~] vim-cmd vmsvc/getallvms
Vmid Name File Guest OS Version Annotation
2 Windows-PC1 [Micron-SSD-894GB] Windows-PC1/Windows-PC1.vmx windows9_64Guest vmx-14

# Set $vmid according to result(s) above
vmid=2

# Unregister VM
[brucelee@esx02:~] vim-cmd /vmsvc/unregister $vmid

Easy lap, right? This is a lot simpler than a 10-page of GUI screenshots, more likely to save your Change Control Board members’ time so they can enjoy other items on the agenda. With such competence and efficiency, one could even move to entertain Boardies with Operation Kung Fu Panda (taking breaks for Chinese food), or something.

PowerShell: List Currently Logon Users On Remote Servers

# Show current sessions on a list of servers
$servers="SHERVER005","SHERVER007";
$servers|%{"$_`n$(query user /server:$_|Out-String)"}
# Sample Output
PS C:\Windows\system32> $servers|%{"$_`n$(query user /server:$_|Out-String)"}
SHERVER005
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
user001 6 Disc 9+08:54 10/10/2019 4:42 AM
user002 9 Disc 3+01:12 10/16/2019 12:56 PM
user003 15 Disc 1:48 10/17/2019 2:29 PM
user004 21 Disc 2:34 10/19/2019 7:52 AM
user005 rdp-tcp#40 22 Active 2:38 10/19/2019 8:21 AM
user006 rdp-tcp#43 23 Active . 10/19/2019 2:02 PM

SHERVER007
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
user007 rdp-tcp#8 3 Active 2:38 10/18/2019 10:01 PM
user008 4 Disc 5:43 10/19/2019 7:52 AM
user009 5 Disc 2:51 10/19/2019 10:08 AM
user010 6 Disc 1:48 10/19/2019 11:18 AM

PowerShell: Automating Process of Setting Up a New Windows Machine

# Step 0: Activate Windows
$licenseKey="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"
function activateWindows{
param(
[string]$key
)
$licensingService = get-wmiObject -query "select * from SoftwareLicensingService" -computername $env:computername;
$licensingService.InstallProductKey($key);
sleep 20;
$licensingService.RefreshLicenseStatus();
Get-CimInstance -ClassName SoftwareLicensingProduct|where {$_.PartialProductKey}|select Description, LicenseStatus
}
activateWindows;
# Step 1: Rename the local machine while remaining with its default workgroup
Rename-Computer -NewName $newServername -Force
Restart-Computer -Force
# Step 2: Find the OU of an intended peer server
$peerServername="SHEVER002"
Move-ADObject $peerServername -TargetPath "OU=Quarantine,DC=INTRANET,DC=KIMCONNECT,DC=COM" -WhatIf
# Step 3: Join new machine to domain
$newServername="SHERVER007"
$domain="INTRANET.KIMCONNECT.COM"
$desktopAdmin="Rambo"
$ouPath= "OU=Servers,DC=INTRANET,DC=KIMCONNECT,DC=COM"
$cred = Get-Credential "$domain`\$desktopAdmin"
add-computer -computername $newServername –Domain $domain -Credential $cred -OUPath $ouPath -restart –force
# Step 4: Install  Choco and update PowerShell
if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}
$packages = 'powershell','googlechrome','firefox','adobereader','7zip.install','javaruntime','putty.install','sysinternals'
ForEach ($package in $packages){choco install $package -y};
Restart-Computer -Force;
# Step 5: Perform Windows Update
function updateLocalWindows{
# Prerequisites
function installPrerequisites{
#Import-Module PSWindowsUpdate -force;
#$psWindowsUpdateAvailable=Get-Module PSWindowsUpdate -EA SilentlyContinue;
$psWindowsUpdateAvailable=Get-Module -ListAvailable -Name PSWindowsUpdate -ErrorAction SilentlyContinue;
if (!($psWindowsUpdateAvailable)){
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Confirm:$false | Out-Null;
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted | Out-Null;
Install-Module PSWindowsUpdate -Confirm:$false -Force | Out-Null;
Import-Module PSWindowsUpdate -force | Out-Null;
}
catch{
"Prerequisites not met on $computer.";
}
}
}

function checkPendingReboot{
param([string]$computer=$ENV:computername)

function checkRegistry{
if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -EA Ignore) { return $true }
if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -EA Ignore) { return $true }
if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations -EA Ignore) { return $true }
try {
$util = [wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities"
$status = $util.DetermineIfRebootPending()
if(($status -ne $null) -and $status.RebootPending){
return $true
}
}catch{}
return $false
}

$localhost=$ENV:computername
if ($localHost -eq $computer){
$result=checkRegistry;
}else{
$result=Invoke-Command -ComputerName $computer -ScriptBlock{
param($importedFunc);
[ScriptBlock]::Create($importedFunc).Invoke();
} -ArgumentList ${function:checkRegistry}
}
return $result;
}
installPrerequisites;

# Register the user of Windows Update Service if it has not been registered
$MicrosoftUpdateID="7971f918-a847-4430-9279-4a52d1efe18d"
$registered=$MicrosoftUpdateID -in (Get-WUServiceManager).ServiceID
if (!($registered)){
Add-WUServiceManager -ServiceID 7971f918-a847-4430-9279-4a52d1efe18d -Confirm:$false
}

# Perform Updates
Get-WindowsUpdate -AcceptAll -MicrosoftUpdate -Install -IgnoreReboot;

if (checkPendingReboot){
$warning="There is a pending reboot flag on this host.`n"
$prompt="Please type 'exit' to cancel or 'reboot' to reboot"
$warning;
do{
$userInput=Read-Host -Prompt $prompt;
if ($userInput -match "reboot"){"Restarting command received!";Restart-Computer;} # -match is faster than -like
}while (($userInput -notmatch "reboot") -AND ($userInput -notmatch "(quit|cancel|exit)"))
}else{"Done."}
}
updateLocalWindows;
# Step 6: Remediate Common Vulnerabilities
function remediateVulnerabilities{
"`nVCE-2017-829..."
reg add "HKLM\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_PRINT_INFO_DISCLOSURE_FIX" /v iexplore.exe /t REG_DWORD /d 1 /f
reg add "HKLM\SOFTWARE\WOW6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_PRINT_INFO_DISCLOSURE_FIX" /v iexplore.exe /t REG_DWORD /d 1 /f

"`nCVE-2017-5715..."
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" /v FeatureSettingsOverride /t REG_DWORD /d 0 /f
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" /v FeatureSettingsOverrideMask /t REG_DWORD /d 3 /f

"`nVCE-2017-5753-54..."
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization" /v MinVmVersionForCpuBasedMitigations /t REG_SZ /d "1.0" /f

"`nASLR Hardening Setting for IE..."
reg add "HKLM\SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ALLOW_USER32_EXCEPTION_HANDLER_HARDENING" /v iexplore.exe /t REG_DWORD /d 1 /f
reg add "HKLM\SOFTWARE\WOW6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ALLOW_USER32_EXCEPTION_HANDLER_HARDENING" /v iexplore.exe /t REG_DWORD /d 1 /f

"`nRemediate MS11-025 MFC Remote Code Execution..."
$minVersion=14
$vcVersions=(Get-ItemProperty HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | where {$_.displayname -like "Microsoft Visual C++*"} | Select-Object DisplayVersion)
foreach ($version in $vcVersions){
if($version.DisplayVersion -ge $minVersion){$safeFlag=$True;}
}
if (!($safeFlag)){
try{
New-Item -ItemType Directory -Force -Path C:\Temp
(new-object System.Net.WebClient).DownloadFile('https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x64.exe','C:\Temp\vcredist_x64.exe')
C:\Temp\vcredist_x64.exe /quiet /norestart
}
catch{
"Unable to download Visual C++"
}
}

"`nSecuring Remote Desktop..."
function secureRDP{
# Remote Desktop Services: Enable NLA Requirement
(Get-WmiObject -class "Win32_TSGeneralSetting" -Namespace root\cimv2\terminalservices -Filter "TerminalName='RDP-tcp'").SetUserAuthenticationRequired(1)

# Remote Desktop Services: Require 'High' level of encryption - FIPS compliant
(Get-WmiObject -class "Win32_TSGeneralSetting" -Namespace root\cimv2\terminalservices -Filter "TerminalName='RDP-tcp'").SetEncryptionLevel(4)
}
secureRDP;

"`nUpdating Windows Defender..."
try{"`nUpdating Windows Defender Antimalware Virus Definitions...";Update-MPSignature;}
catch{"Cannot update Windows Defender."}

"`nFix Unquoted Service Path Enumerations..."
function fixUnquotedServicePathEnum{
$fixScriptDestination="C:\Temp\Windows_Path_Enumerate.ps1"
$fixScriptDownload="https://gallery.technet.microsoft.com/scriptcenter/Windows-Unquoted-Service-190f0341/file/136821/7/Windows_Path_Enumerate.ps1"
(new-object System.Net.WebClient).DownloadFile($fixScriptDownload, $fixScriptDestination)
C:\Temp\Windows_Path_Enumerate.ps1 -FixUninstall -FixEnv
}
fixUnquotedServicePathEnum;

"`nApplying IIS Crypto Templates..."
function installIISCrypto{
# Download iisCrypto
New-Item -ItemType Directory -Force -Path C:\Temp
$url = "https://kimconnect.com/wp-content/uploads/2019/05/IISCryptoCli.zip"
$temp = "C:\Temp\IISCryptoCli.zip"
(new-object System.Net.WebClient).DownloadFile($url,$temp)

$destination="C:\Windows"
expand-archive -path $temp -destinationpath $destination
}
installIISCrypto;

function applyIISCrypto{
$templateValues="pci32","best","strict","fips140","default"
$count=$templateValues.length
for ($i=0; $i -lt $count; $i++) { $template=$templateValues[$i];"$i`: $template"}
$index=Read-Host -Prompt "Type in the Template Index NUMBER from 0 to $($count-1) to apply"
if ($index -lt $count){
$iisCryptoExist=([System.IO.File]::Exists("C:\Windows\IISCryptoCli.exe"))
if ($iisCryptoExist){
$choice=$templateValues[$index]
"`nBacking up registry into C:\backup.reg, and applying $choice template..."
IISCryptoCli /backup C:\backup.reg /template $choice;
"`nIISCrypto $choice template has been applied...`nPlease reboot machine for changes to take effect."
}
else{
"`nIISCryto wasn't installed. Retrying..."
installIISCripto;
applyIISCrypto;
}
}
else{"Index number was not recognized. Thus, IISCrypto Template was NOT applied."}
}
applyIISCrypto;

# Disable SMB1
Disable-WindowsOptionalFeature -Online -FeatureName smb1protocol -InformationAction SilentlyContinue -NoRestart

}
# Step 7: Optimize & Clean Windows
function optimizeWindows{
"`nSet Power Options..."
function setPowerMax{
powercfg /setactive SCHEME_MIN
powercfg /hibernate off
}
setPowerMax;

"Disable Automatic Startup Repair..."
cmd.exe /c "bcdedit /set {default} recoveryenabled No"
cmd.exe /c "bcdedit /set {default} bootstatuspolicy ignoreallfailures"
}

function removeBloatware{
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$bloatwareRemovalDownload="https://github.com/Sycnex/Windows10Debloater/archive/master.zip"
$bloatwareRemovalDestination="C:\Temp\Windows10Debloater-master.zip"
(New-Object System.Net.WebClient).DownloadFile($bloatwareRemovalDownload, $bloatwareRemovalDestination)
$destination="C:\Temp"
expand-archive -path $bloatwareRemovalDestination -DestinationPath $destination
PowerShell.exe -executionpolicy bypass -File C:\Temp\Windows10Debloater-master\Windows10Debloater.ps1 -Confirm:$False

# Disable Windows Media (a vector of attack surface from malware)
Disable-WindowsOptionalFeature –FeatureName "WindowsMediaPlayer" -Online

# Disable XPS
Disable-WindowsOptionalFeature -Online -FeatureName "Printing-XPSServices-Features"

# Workfolder Client
Disable-WindowsOptionalFeature -Online -FeatureName "WorkFolders-Client"

# Remove Windows Store
Get-AppxPackage -AllUsers | Where-Object {$_.Name -like "Microsoft.WindowsStore*"} | remove-appxpackage
}
removeBloatware;

function cleanWindows{
"Clear Windows Update Cache..."
Dism.exe /online /Cleanup-Image /StartComponentCleanup

"Delete files in Temp directory..."
del C:\Temp\*.* -Recurse -Force

"Prune Event Logs..."
wevtutil el | Foreach-Object {wevtutil cl "$_"}

"Performing Disk Cleanup..."
$HKLM = [UInt32] "0x80000002"
$strKeyPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches"
$strValueName = "StateFlags0065"

$subkeys = gci -Path HKLM:\$strKeyPath -Name
ForEach ($subkey in $subkeys) {
New-ItemProperty -Path HKLM:\$strKeyPath\$subkey -Name $strValueName -PropertyType DWord -Value 2 -ErrorAction SilentlyContinue| Out-Null
Start-Process cleanmgr -ArgumentList "/sagerun:65" -Wait -NoNewWindow -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}
ForEach ($subkey in $subkeys) {
Remove-ItemProperty -Path HKLM:\$strKeyPath\$subkey -Name $strValueName | Out-Null
}
}
cleanWindows;

PowerShell: Activate Remote Windows

$remoteWindows="SHERVER01","SHERVER02"
$licenseKey="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"

function activateWindows{
param(
[string]$key
)
$licensingService = get-wmiObject -query "select * from SoftwareLicensingService" -computername $env:computername;
$licensingService.InstallProductKey($key);
sleep 20;
$licensingService.RefreshLicenseStatus();
Get-CimInstance -ClassName SoftwareLicensingProduct|where {$_.PartialProductKey}|select Description, LicenseStatus
}

Invoke-Command -computer $remoteWindows -ScriptBlock{
param($importedFunc,$importedKey)
[ScriptBlock]::Create($importedFunc).Invoke($importedKey);
} -Args ${function:activateWindows},$licenseKey

#Validate licensing status
Get-CimInstance -computername $remoteWindows -ClassName SoftwareLicensingProduct|where {$_.PartialProductKey}|select Description, LicenseStatus

<# Sample Output
Description LicenseStatus
----------- -------------
Windows(R) Operating System, VOLUME_MAK channel 1
Windows(R) Operating System, VOLUME_MAK channel 1
Windows(R) Operating System, VOLUME_MAK channel 1
Windows(R) Operating System, VOLUME_MAK channel 1
Windows(R) Operating System, VOLUME_MAK channel 1
Windows(R) Operating System, VOLUME_MAK channel 1
Windows(R) Operating System, VOLUME_MAK channel 1
Windows(R) Operating System, VOLUME_MAK channel 1
#>

PowerShell: Execute as System Elevated Session with a Domain Administrator account

Part 1: Relaunch script in the context of Elevated System privileges:

############# Ensure that Program executes in the context of a Local System Administrator ################
# Get the ID and security principal of the current user account
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$currentUser=$myWindowsID.Name
$myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)

# Get the security principal for the Administrator role
$adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator

# Check to see if we are currently running "as Administrator"
if ($myWindowsPrincipal.IsInRole($adminRole))
{
# We are running "as Administrator" - so change the title and background color to indicate this
$Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)"
$Host.UI.RawUI.BackgroundColor = "Black"
clear-host
}
else
{
# We are not running "as Administrator" - so relaunch as administrator

# Create a new process object that starts PowerShell
$newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";

# Specify the current script path and name as a parameter
$newProcess.Arguments = $myInvocation.MyCommand.Definition;

# Indicate that the process should be elevated
$newProcess.Verb = "runas";

# Start the new process
[System.Diagnostics.Process]::Start($newProcess);

# Exit from the current, unelevated, process
exit
}

function validateCurrentAccountAsDomainAdmin{
if((whoami /groups) -match 'domain admins'){
#"This account is a Domain Admins member";
return $True;
}else{
#"This account is NOT a Domain Admins member";
return $False;
}
}

Write-Host -NoNewLine "Running as $currentUser in context of Elevated System Permissions..."
Write-Host "$currentUser $(if(validateCurrentAccountAsDomainAdmin){"is"}else{"not"}) a Domain Admin."
##########################################################################################################

############# Ensure that Program executes in the context of a Domain Administrator ######################

Part 2: Obtain domain admin $CRED for subsequent Invoke-Command operations:

############# Ensure that Program executes in the context of a Domain Administrator ######################

# This function is will export a global variable $domainAdminCred of a Domain Administrator account
Function getDomainAdminCred{

# Add pre-requisites and obtain list of domain admins
if (!(Get-Module ActiveDirectory)){
Import-Module ServerManager;
Add-WindowsFeature RSAT-AD-PowerShell;
Import-Module ActiveDirectory;
}

# Set domain variables
$currentUser=[System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$userDomain=$env:USERDOMAIN
$domainObject = "LDAP://" + $env:userdnsdomain
$domainAdmins=Get-ADGroupMember -Identity "Domain Admins" -Recursive | %{Get-ADUser -Identity $_.distinguishedName} | Where-Object {$_.Enabled -eq $True}|%{"$userDomain\$($_.SamAccountName)"}

# Check whether a given username matches the list of Domain Admins
function validateDomainAdminMembership{
param (
[string]$username=$currentUser
)
if($domainadmins|?{$_ -eq $username}){
Write-Host "$username is a Domain Admin";
return $True;
}else{
Write-Host "$username not a Domain Admin.";
return $False;
}
}

function testCredential{
param (
[string]$username=$currentUser,
$encryptedPassword=$currentUserPassword
)
$plaintextPassword = (New-Object System.Management.Automation.PSCredential 'N/A',$encryptedPassword).GetNetworkCredential().Password
$domainBindTest = (New-Object System.DirectoryServices.DirectoryEntry($domainObject,$username,$plaintextPassword)).DistinguishedName
if ($domainBindTest){return $True;} else{Return $False;}
}

function validateDomainAdminCred{
$global:domainAdminCred=$False;

function loopUntilCred{
# Get the domain admin account name
do {
$newID=Read-Host -Prompt 'Input a domain admin username'
if (!($newID -match $userDomain)){$newID="$userDomain\$newID"} # Add domain prefix to userID if input doesn't already contain such
$domainAdminMatched=validateDomainAdminMembership $newID;
} until ($domainAdminMatched)

# Get the correct password
do {
$newPassword = Read-Host -assecurestring "Please enter the password for $newID"
#$providedPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
#$providedCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $providedID,$providedPassword
$goodCredential=testCredential -username $newID -encryptedPassword $newPassword
if($goodCredential){
"Alternate Domain Admin Credential validated!";
$global:domainAdminCred=New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $newID,$newPassword;
}
else{
"Password doesn't match.";
}
} until($goodCredential)
}

if (validateDomainAdminMembership){
$currentUserPassword=Read-Host -assecurestring "Please enter the current user's password";
if(testCredential -encryptedPassword $currentUserPassword){
"Domain Admin cred with current account has been validated!";
$global:domainAdminCred=New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $currentUser,$currentUserPassword;
}else{loopUntilCred;}
}else{
loopUntilCred;
}
}
validateDomainAdminCred;
}

function computerIsDomainJoined{
if ((gwmi win32_computersystem).partofdomain -eq $true) {
write-host -fore green "$ENV:computername is domain joined!"
return $true;
} else {
write-host -fore red "$ENV:computername is on a workgroup!"
return $false;
}
}

if (computerIsDomainJoined){
getDomainAdminCred;
}else{"This computer is not joined to a domain. Thus no Domain Admin credentials are expected."}
##########################################################################################################