PowerShell: Get Size On Disk and File System Discover

# getSizeOnDisk.ps1

# Manually entered variables
$clusterSizeOfNetworkShare=16384
$directory="C:\"
$excludeDirectories="C:\Windows","C:\Program Files","C:\Program Files (x86)"

################################## Excuting Program as an Administrator ####################################
# Get the ID and security principal of the current user account
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$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
}

Write-Host -NoNewLine "Running as Administrator..."

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;
################################## Excuting Program as an Administrator ####################################

# Autogen variables
$getFolders=(get-childitem -Directory $directory|select FullName).FullName
$folders=compare-object -ReferenceObject $getFolders -DifferenceObject $excludeDirectories -PassThru
#$driveLetterAndBlockSize=Get-CimInstance -ClassName Win32_Volume|Select-Object DriveLetter,BlockSize|?{$_.DriveLetter -ne $null -and $_.BlockSize -ne $null}

function getSizeOnDisk{
param (
[string]$path='.',
$clusterSizeOfNetworkShare=4096
)

$source = @"
using System;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.IO;

namespace Win32
{

public class Disk {

[DllImport("kernel32.dll")]
static extern uint GetCompressedFileSizeW([In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
[Out, MarshalAs(UnmanagedType.U4)] out uint lpFileSizeHigh);

public static ulong GetSizeOnDisk(string filename)
{
uint HighOrderSize;
uint LowOrderSize;
ulong size;

FileInfo file = new FileInfo(filename);
LowOrderSize = GetCompressedFileSizeW(file.FullName, out HighOrderSize);

if (HighOrderSize == 0 && LowOrderSize == 0xffffffff)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
else {
size = ((ulong)HighOrderSize << 32) + LowOrderSize;
return size;
}
}
}
}

"@

Add-Type -TypeDefinition $source

$totalSize=0;
$count=0;
$driveLetter=(Get-Item $path).PSDrive.Root
$clusterSize=(Get-WmiObject -Class Win32_Volume | Where-Object {$_.Name -eq $driveLetter}).BlockSize
if(!($clusterSize)){$clusterSize=$clusterSizeOfNetworkShare}

Get-ChildItem $path -Recurse -Force -ErrorAction SilentlyContinue| where { !$_.PSisContainer }|% {
$size = $(try{[Win32.Disk]::GetSizeOnDisk([string]$_.FullName)}catch{})
#$AFZ=[MidpointRounding]::AwayFromZero
#$roundedSize=[math]::Round($size/$ClusterSize+0.5,$AFZ)*$ClusterSize
if ($size){
if ($size -gt $clusterSize){$roundedSize=[math]::ceiling($size/$clusterSize)*$clusterSize}
if ($size -le $clusterSize){$roundedSize=$clusterSize;}
}else{
$roundedSize=0;
}
$count=$count+1
#"$count`. $($_.FullName): $size vs $roundedSize"
$totalSize+=$roundedSize
}

#$foldersCount=(Get-ChildItem $path -Recurse -Force | where { $_.PSisContainer }).Count
#write-output "$count files $foldersCount folders total size: $totalSize"
return $totalSize
}

# List top 10 files in all folders
function getLargestFiles{
param($folders,$limit)
$topFiles=$folders|%{get-childitem $_ -recurse}| ?{!$_.PSisContainer}|sort -Property Length -Descending | select-object -first $limit|select FullName,Length
return $topFiles
}
$top10Files=getLargestFiles -folders $folders -limit 10

function discoverFolderSizes{
param($folders)
$folderAndSizes=$folders|select @{Name="Path";Expression={$_}},
@{Name="SizeOnDiskBytes";Expression={[math]::round($(getSizeOnDisk -path $_),2)}}
return $folderAndSizes;
}
$folderSizes=discoverFolderSizes -folders $folders

# Find largest folder
$biggestFolder=[math]::round(($folderSizes|sort -Property SizeOnDiskBytes -Descending|select-object -first 1)/1GB,2)

# List 10 biggest files in the biggest folder
$top10FilesInBiggestItems=get-childitem $biggestFolder.Path -recurse | ?{!$_.PSisContainer}|sort -Property Length -Descending | select-object -first 10|select FullName,Length

# Display output
$biggestFolder;
$top10FilesInBiggestItems;
$top10Files;

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;
$osArchitecture=$ENV:PROCESSOR_ARCHITECTURE
$exeInfo | Add-Member -MemberType NoteProperty -Name "osName" -Value $osName
$exeInfo | Add-Member -MemberType NoteProperty -Name "osArchitecture" -Value $osArchitecture
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;
$osArchitecture=$exeInfo.osArchitecture;
return "$computername`: $osName $osArchitecture $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: 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: 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: Create Daily VSS Snapshot of Volumes on Local Windows Machine

<# VSS-Snapshots-on-Microsoft-File-Server-Clusters.ps1

Functions:
1. Remove all previous snapshots that are older than retention period
2. Dynamically detect all volumes on local machine
3. Dynamically take snapshots of each volume and mount them into C:\snapshots\$hostname\$volumeLabel\$snapshotTimeStamp (i.e. \\FILESHERVER01\Snapshots\HOSTNAME007\VOLUMELABEL007)

Limitations:
1. VSS must be available on the target Windows machine
2. PowerShell version 3.0 or higher is assumed

Technical considerations:
1. This program should not be used to replace an Enterprise Grade servers backup system (e.g. Veeam, Veritas, Rubik).
2. Locally mounted snapshots will NOT be recoverable when the server itself becomes inoperable.

Quick Notes:
# command to delete all VSS Snapshots on the local system
vssadmin delete shadows /all /Quiet
#>

# Init variable to store local volume labels: this returns all fixed local volumes, excluding CD Roms, USB drives, and C:\
#$cdRomDrives=Get-CimInstance Win32_LogicalDisk | ?{ $_.DriveType -eq 5} | select DeviceID
$driveLettersExclusion="[C]\:"
#$localVolumes=(Get-CimInstance Win32_LogicalDisk | ?{ $_.DriveType -eq 3}).DeviceID|Where{$_ -notmatch $driveLettersExclusion} #requires PowerShell version 3.0+
$localVolumes=Get-WmiObject Win32_LogicalDisk | ?{ $_.DriveType -eq 3}|Where{$_.DeviceID -notmatch $driveLettersExclusion}|Select DeviceID|%{$_.DeviceID} #PowerShell 2.0 compatible

# Set snapshot root directory variable
$localSnapshotDirectory="C:\Snapshots"
$remoteSnapshotDirectory="\\Snapshots\FileServerClusters"

# Set Retention Period
$retentionPeriod=30;

# Set hostname
$hostname=$env:computername

################################## Excuting Program as an Administrator ####################################
# Get the ID and security principal of the current user account
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$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
}

Write-Host -NoNewLine "Running as Administrator..."
################################## Excuting Program as an Administrator ####################################

# Adding Prerequisite Microsoft Cluster
Function importModuleFailoverClusters{
if (!(get-module -Name "FailoverClusters") ){
Try{
Import-Module FailoverClusters | out-null;
}
catch{
# On error, install the missing module
Install-WindowsFeature RSAT-Clustering-MGMT | out-null;
Install-WindowsFeature RSAT-Clustering-PowerShell | out-null;
Import-Module FailoverClusters | out-null;
}
}
}

function enableR2RSymbolicLinks{
<# Preemptively resolve this error
\\FILESHERVER\SomeShare is not accessible. You might not have permission to use this network resource. Contact the administrator of this server to find out if you have access permissions.
The symbolic link cannot be followed because its type is disabled.
#>

# Enable Remote to remote symlink following if it's not already set
$r2rEnabled=fsutil behavior query SymlinkEvaluation | select-string -Pattern "Remote to remote symbolic links are enabled."
$r2lEnabled=fsutil behavior query SymlinkEvaluation | select-string -Pattern "Remote to local symbolic links are enabled."
if (!($r2rEnabled) -or !($r2lEnabled)){
write-host "`Symbolic links following is disabled.`nEnabling this feature..."
fsutil behavior set SymlinkEvaluation R2R:1;
fsutil behavior set SymlinkEvaluation R2L:1
write-host "`nThis is now the system's settings after the change:"
fsutil behavior query SymlinkEvaluation
}
}


function checkDiskFree{
[cmdletbinding()]
param(
[string]$volume="C:\",
[string]$targetNode="localhost"
)

<#
Excerpt from http://technet.microsoft.com/en-us/library/ee692290(WS.10).aspx:

"For volumes less than 500 megabytes, the minimum is 50 megabytes of free space.
For volumes more than 500 megabytes, the minimum is 320 megabytes of free space.
It is recommended that least 1 gigabyte of free disk space on each volume if the volume size is more than 1 gigabyte."

#>

# Import variables
$thisNode=$targetNode

# Fix targetVolume input if it's missing the suffix
if ($volume -like "*\"){$thisVolume=$volume.Substring(0,$volume.Length-1)}else{$thisVolume=$volume}

# Obtain disk information
$diskObject = Get-WmiObject Win32_LogicalDisk -ComputerName $thisNode -Filter "DeviceID='$thisVolume'"
$diskFree=[Math]::Round($diskObject.FreeSpace / 1MB)
$diskSize=[Math]::Round($diskObject.Size / 1MB)

switch ($diskSize){
{$diskSize -ge 1024} {if ($diskFree -gt 1024){$feasible=$True;}else{$feasible=$False;};;break;}
{$diskSize -ge 500} {if ($diskFree -gt 320){$feasible=$True;}else{$feasible=$False;};;break;}
{$diskSize -lt 500} {if ($diskFree -gt 50){$feasible=$True;}else{$feasible=$False;};break;}
}

return $feasible
}

function removeSnapshots{
[cmdletbinding()]
param(
[int]$retentionPeriod=30
)

# Remove links older than X days
function removeLinks{
[cmdletbinding()]
param(
[string]$snapshotRootDirectory="C:\Snapshots",
[int]$retentionPeriod=30
)

# Remove dead links
$allSymlinks=Get-ChildItem $snapshotRootDirectory -Recurse -Depth 3 -ErrorAction SilentlyContinue|Where-Object {($_.Attributes -match "ReparsePoint")}
$allSymlinks|%{ if(!(Test-Path "$($_.Fullname)\*")){
(Get-Item $_.Fullname).Delete();
Write-Host "Removed dead link: $_";
}
}

# Remove parent directory of dead links (empty directories)
$parentDirectories=Get-ChildItem $snapshotRootDirectory\*\* -ErrorAction SilentlyContinue
$parentDirectories|%{
$directoryChildren = Get-ChildItem $_.FullName
if ($directoryChildren.count -eq 0){
(Get-Item $_.Fullname).Delete();
Write-Host "Removed empty folder: $($_.Fullname)";
}}

# Remove remaining expired links
$remainingSymlinks=Get-ChildItem $snapshotRootDirectory -Recurse -Depth 3 -ErrorAction SilentlyContinue|Where-Object {($_.Attributes -match "ReparsePoint")}
foreach ($link in $remainingSymlinks){
$creationTime=$link.CreationTime
$thisSymlink=$link.FullName
$removeSymlink=$creationTime -lt (Get-Date).AddDays(-$retentionPeriod)

# Remove symlink if condition is true
if ($removeSymlink){
(Get-Item $thisSymlink).Delete()
"Shadow link $thisSymlink with creation time of $creationTime has been removed."
}else{
"Shadow link $thisSymlink with creation time of $creationTime has NOT been removed."
}
}
}

# Remove old snapshots
function deleteOldSnapshots{
[cmdletbinding()]
param(
[int]$olderThanXDays=365
)
$allSnapshots=Get-WmiObject -Class win32_shadowcopy|?{$_.ClientAccessible -eq $true}
#$driveLetterSnapshotsAssociation = Get-WmiObject Win32_Volume -Filter "DriveType=3" |?{$_.DriveLetter -ne $null}| select DriveLetter,Label, DeviceID | Sort DriveLetter;

$allSnapshots | ForEach-Object {
$snapshotDate = $_.InstallDate
$snapshotID = $_.ID
#$snapshotID = {[void]($_.ID -match "^{(.*)}$");$matches[1]}.Invoke()
#$volumePath = "$($driveLetterSnapshotsAssociation.DriveLetter[($driveLetterSnapshotsAssociation.DeviceID).IndexOf($_.VolumeName)]):"
$snapshotDateTimeValue = [management.managementDateTimeConverter]::ToDateTime($snapshotDate)
$thisClientAccessibleValue = $_.ClientAccessible
$currentDate = Get-Date
$timeSpan = New-TimeSpan $snapshotDateTimeValue $currentDate
$days = $timeSpan.Days

If ($days -gt $retentionPeriod) {
#$_.Delete() # does nothing
#invoke-expression "vssadmin delete shadows /for=$volumePath /shadow=$snapshotID /quiet" # doesn't work, must invoke from cmd.exe
cmd.exe /c vssadmin delete shadows /Shadow=$snapshotID /quiet
"$snapshotID with date stamp of $snapshotDate has been DELETED as it's older than $retentionPeriod days."
} else{
"$snapshotID with date stamp of $snapshotDate has NOT been deleted as it's newer than $retentionPeriod days."
}
}
}

removeLinks -snapshotRootDirectory $remoteSnapshotPath -retentionPeriod $retentionPeriod
removeLinks -snapshotRootDirectory $localSnapshotPath -retentionPeriod $retentionPeriod
deleteOldSnapshots -retentionPeriod $retentionPeriod
}

function createNewSnapshot{
[cmdletbinding()]
param(
[string]$targetVolume="C:\",
[string]$label=$volumeLabel,
[string]$localLinkPrefix,
[string]$transformedLocalLinkPrefix,
[string]$remoteLinkPrefix
)

# Create directories if they don't already exist
#$remoteLinkParentDirectory=split-path $remoteLink -Parent
#$localSnapshotParentDirectory=split-path $localShapshot -Parent
New-Item -ItemType Directory -Force -Path $localLinkPrefix | Out-Null;
New-Item -ItemType Directory -Force -Path $remoteLinkPrefix | Out-Null;

# Fix targetVolume input if it's missing the suffix
if (!($targetVolume -like "*\")){$targetVolume+="\"}

#write-host "please check these links:`nlocalSnapshotPath: $localSnapshotPath`ntransformedLocalPath: $transformedLocalPath`nremoteLink: $remoteLink"

# Create the VSS snapshot
$shadowCopyClass=[WMICLASS]"root\cimv2:win32_shadowcopy";
$thisSnapshot = $shadowCopyClass.Create($targetVolume, "ClientAccessible");
$thisShadow = Get-WmiObject Win32_ShadowCopy | Where-Object { $_.ID -eq $thisSnapshot.ShadowID };
$shadowTimeStamp = $thisShadow.ConvertToDateTime($thisShadow.InstallDate).ToString('yyyy-MM-dd_hh.mm.ss');
$thisShadowPath = $thisShadow.DeviceObject + "\";

$localShapshot="$localLinkPrefix\$shadowTimeStamp";
$remoteLink="$remoteLinkPrefix\$shadowTimeStamp";
$transformedLocal="$transformedLocalLinkPrefix\$shadowTimeStamp";

# Create links toward this snapshot
cmd /c mklink /J $localShapshot $thisShadowPath;
cmd /c mklink /d $remoteLink $transformedLocal;

<# PowerShell version 5.0 required
+-----------------------+-----------------------------------------------------------+
| mklink syntax | Powershell equivalent |
+-----------------------+-----------------------------------------------------------+
| mklink Link Target | New-Item -ItemType SymbolicLink -Name Link -Target Target |
| mklink /D Link Target | New-Item -ItemType SymbolicLink -Name Link -Target Target |
| mklink /H Link Target | New-Item -ItemType HardLink -Name Link -Target Target |
| mklink /J Link Target | New-Item -ItemType Junction -Name Link -Target Target |
+-----------------------+-----------------------------------------------------------+

SymbolicLink (modern) supports UNC paths, while Junction (older) does not.

Junction has one feature that symlinks do not have: functional Remote-to-Local Following!
This has made it possible to mount local snapshots as UNC paths. The trick is to combine
the features of both junctions and symlinks.
#>
"Snapshot of $targetVolume has been made and it's accessible at this path: $remoteLink"

# Export variables that this specific snapshot can be targeted and removed
#$GLOBAL:shadow=$thisShadow;
#$GLOBAL:snapshot=$thisSnapshot;
}

Function rebuildVssLinks{
Function gatherSnapshots{
param(
$hostname=$env:computername,
$localLinkPrefix="C:\snapshots\$(try{(get-cluster).name}catch{"NoCluster"})\$env:computername",
$remoteLinkPrefix="\\Snapshots\FileServerClusters\$(try{(get-cluster).name}catch{"NoCluster"})\$env:computername"
)
# This variable holds the association between drive letters and shadow IDs
$driveLetterSnapshotsAssociation = Get-WmiObject Win32_Volume -Filter "DriveType=3" |?{$_.DriveLetter -ne $null}| select DriveLetter,Label, DeviceID | Sort DriveLetter;

# Drive letter to labeling map (redundant and deprecated)
# $driveLettersAndLabels=get-volume |?{$_.DriveLetter -ne $null -and $_.DriveLetter -notmatch 'C'} | select DriveLetter,FileSystemLabel|sort -Property DriveLetter

# This variable holds all client accessible snapshots
$clientAccessibleSnapshots=Get-WmiObject -Class win32_shadowcopy|?{$_.ClientAccessible -eq $true}
$snapshotsCount=$clientAccessibleSnapshots.Count

function generateLink{
param(
$snapshotObject=(Get-WmiObject -Class win32_shadowcopy|?{$_.ClientAccessible -eq $true})[0],
$snapshotToDriveLetter=(Get-WmiObject Win32_Volume -Filter "DriveType=3" |?{$_.DriveLetter -ne $null}| select DriveLetter,Label, DeviceID | Sort DriveLetter),
$linkPrefix="c:\snapshots\$(try{(get-cluster).name}catch{"NoCluster"})\$env:computername"
)
$volumeLetter=($snapshotToDriveLetter.DriveLetter[($snapshotToDriveLetter.DeviceID).IndexOf($snapshotObject.VolumeName)])[0];
$volumeLabel=$snapshotToDriveLetter.Label[($snapshotToDriveLetter.DriveLetter).IndexOf($volumeLetter)];
$folderName="Volume_$volumeLetter`_$((get-volume -DriveLetter $volumeLetter).FileSystemLabel)";
if (!($folderName)){$folderName="Volume_$volumeLetter"};
$timeStamp=$snapshotObject.ConvertToDateTime($snapshotObject.InstallDate).ToString('yyyy-MM-dd_hh.mm.ss');
return "$linkPrefix\$folderName\$timeStamp";
}

# Initialize Array
if ($snapshotsCount -gt 1){
Write-Host "Processing $snapshotsCount snapshot..."
Write-Host "Gathering information on 1 of $snapshotsCount..."
$initSnapshot=$clientAccessibleSnapshots[0];

$GLOBAL:snapshots=@{}
$snapshots["targetPath"] = @{}; $snapshots["localLink"] = @{}; $snapshots["remoteLink"] = @{};
$snapshots["targetPath"] = @("$($initSnapshot.DeviceObject)");
$snapshots["localLink"] = @("$(generateLink -snapshotObject $initSnapshot -snapshotToDriveLetter $driveLetterSnapshotsAssociation -linkPrefix $localLinkPrefix)")
$snapshots["remoteLink"] = @("$(generateLink -snapshotObject $initSnapshot -snapshotToDriveLetter $driveLetterSnapshotsAssociation -linkPrefix $remoteLinkPrefix)")
#$snapshots["installDate"] = @{}; $snapshots["installDate"] = @($initSnapshot.ConvertToDateTime($initSnapshot.InstallDate));
#$snapshots["timeStamp"] = @{}; $snapshots["timeStamp"] = @($initSnapshot.ConvertToDateTime($initSnapshot.InstallDate).ToString('yyyy-MM-dd_hh.mm.ss'));
#$snapshots["id"] = @{}; $snapshots["id"] = @({[void]($initSnapshot.ID -match "^{(.*)}$");$matches[1]}.Invoke());
#$snapshots["volumeLetter"] = @{}; $snapshots["volumeLetter"] = @($driveLetterSnapshotsAssociation.DriveLetter[($driveLetterSnapshotsAssociation.DeviceID).IndexOf($initSnapshot.VolumeName)])

# Load the rest of the values
#for ($i=1; $i -lt 2; $i++){
for ($i=1; $i -lt $snapshotsCount; $i++){
Write-Host "Gathering information on $($i+1) of $snapshotsCount..."
$snapshots["targetPath"] += "$($clientAccessibleSnapshots[$i].DeviceObject)";
$snapshots["localLink"] += "$(generateLink -snapshotObject $clientAccessibleSnapshots[$i] -snapshotToDriveLetter $driveLetterSnapshotsAssociation -linkPrefix $localLinkPrefix)";
$snapshots["remoteLink"] += "$(generateLink -snapshotObject $clientAccessibleSnapshots[$i] -snapshotToDriveLetter $driveLetterSnapshotsAssociation -linkPrefix $remoteLinkPrefix)";
}
}else{
$snapshots=@{}
$snapshots["targetPath"] = @{}; $snapshots["targetPath"] = @($clientAccessibleSnapshots.DeviceObject);
$snapshots["localLink"] = @{}; $snapshots["localLink"] = @(generateLink -snapshotObject $clientAccessibleSnapshots -snapshotToDriveLetter $driveLetterSnapshotsAssociation -linkPrefix $localLinkPrefix)
$snapshots["remoteLink"] = @{}; $snapshots["remoteLink"] = @(generateLink -snapshotObject $clientAccessibleSnapshots -snapshotToDriveLetter $driveLetterSnapshotsAssociation -linkPrefix $remoteLinkPrefix)
}
write-host "An object named `$`snapshots is now loaded with these properties: targetPath, localLink, and remoteLink.";
return $snapshots;
}

Function rebuildLinks{
param($snapshotLinks=(gatherSnapshots))
$targets=$snapshotLinks.targetPath;
$localLinks=$snapshotLinks.localLink;
$remoteLinks=$snapshotLinks.remoteLink;

for ($i=0;$i -lt $localLinks.count; $i++){
$thisLocalLink="$($localLinks[$i])\";
$validLocalLink=test-path "$thisLocalLink*";
if(!($validLocalLink)){
$thisTarget="$($targets[$i])\";
write-host "validate this target $thisTarget.";
cmd /c mklink /J $thisLocalLink $thisTarget;
write-host "$thisLocalLink has been regenerated.";
}

$thisRemoteLink=$remoteLinks[$i];
write-host "validate this remote link $thisRemoteLink.";
$validRemoteLink=test-path "$thisRemoteLink\*"
if(!($validRemoteLink)){
cmd /c mklink /d $thisRemoteLink $thisLocalLink;
write-host "$thisRemoteLink has been regenerated.";
}

}
}

rebuildLinks;
}

function proceed{
importModuleFailoverClusters;
$GLOBAL:clusterName=(get-cluster).name
$GLOBAL:localSnapshotPath="$localSnapshotDirectory\$clusterName"
$GLOBAL:remoteSnapshotPath="$remoteSnapshotDirectory\$clusterName"

if ($clusterName){
New-Item -ItemType Directory -Force -Path $remoteSnapshotPath | Out-Null;
New-Item -ItemType Directory -Force -Path $localSnapshotPath | Out-Null;
enableR2RSymbolicLinks;
removeSnapshots -daysOlderThan $retentionPeriod;
rebuildVssLinks;

if ((get-item $localSnapshotPath) -and (get-item $remoteSnapshotPath) -and ($localVolumes)){
$localVolumes|%{
$GLOBAL:volumeLetter="$_"[0];
$GLOBAL:volumelabel="Volume_$volumeLetter`_$((get-volume -DriveLetter $volumeLetter).FileSystemLabel)";
#$GLOBAL:timeStamp=Get-Date -Format 'yyyy-MM-dd_hh.mm.ss'
if (!($volumeLabel)){$volumelabel="Volume_$volumeLetter"};
$localLinkPrefix="$localSnapshotPath\$hostname\$volumeLabel";
$transformedLocalLinkPrefix="\\$hostname\C$\Snapshots\$clusterName\$hostname\$volumeLabel"
$remoteLinkPrefix="$remoteSnapshotPath\$hostname\$volumeLabel"

if(checkDiskFree -volume $_){
createNewSnapShot -targetVolume $_ -label $volumeLabel -localLinkPrefix $localLinkPrefix -transformedLocalLinkPrefix $transformedLocalLinkPrefix -remoteLinkPrefix $remoteLinkPrefix;
}else{"Volume $_ does NOT have sufficient disk space available for taking snapshots."}
}
}else{"Program aborted due to missing items."}
} else{Write-Host "unable to import FailoverCluster Module"}
}

proceed;
<# Daily-VSS-Snapshot-Windows-Standalone-FileServer.ps1

Functions:
1. Dynamically detect all volumes on local machine
2. Take snapshots of each locally mounted volume (including iSCSI LUNs) and mount them onto C:\Snapshots\$hostname\$volumeLabel\$snapshotTimeStamp
3. Remove all previous snapshots that are older than retention period

Limitations:
1. VSS must be available on the host Windows machine
2. PowerShell version 3.0 or higher is assumed

Technical considerations:
1. This program should not be used to replace an Enterprise Grade servers backup system (e.g. Veeam, Veritas, Rubik).
2. WARNING: locally mounted snapshots will NOT be recoverable when the server itself becomes inoperable.

Quick Notes:
# command to delete all VSS Snapshots on the local system
vssadmin delete shadows /all /Quiet
#>

# Init variable to store local volume labels: this returns all fixed local volumes, excluding CD Roms, USB drives, and C:\
#$cdRomDrives=Get-CimInstance Win32_LogicalDisk | ?{ $_.DriveType -eq 5} | select DeviceID
$driveLettersExclusion="[C]\:"
#$localVolumes=(Get-CimInstance Win32_LogicalDisk | ?{ $_.DriveType -eq 3}).DeviceID|Where{$_ -notmatch $driveLettersExclusion} #requires PowerShell version 3.0+
$localVolumes=Get-WmiObject Win32_LogicalDisk | ?{ $_.DriveType -eq 3}|Where{$_.DeviceID -notmatch $driveLettersExclusion}|Select DeviceID|%{$_.DeviceID} #PowerShell 2.0 compatible

# Set snapshot root directory variable
$localSnapshotDirectory="C:\Snapshots"

# Set Retention Period
$retentionPeriod=30;

# Set hostname
$hostname=$env:computername

################################## Excuting Program as an Administrator ####################################
# Get the ID and security principal of the current user account
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$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
}

Write-Host -NoNewLine "Running as Administrator..."
################################## Excuting Program as an Administrator ####################################

# Adding Prerequisite Microsoft Cluster
Function installFailoverClustersModule{
if (!(get-module -Name "FailoverClusters") ){
Try{
Import-Module FailoverClusters | out-null;
}
catch{
# On error, install the missing module
Install-WindowsFeature RSAT-Clustering-MGMT | out-null;
Install-WindowsFeature RSAT-Clustering-PowerShell | out-null;
Import-Module FailoverClusters | out-null;
}
}
}

function enableR2RSymbolicLinks{
<# Preemptively resolve this error
\\FILESHERVER\SomeShare is not accessible. You might not have permission to use this network resource. Contact the administrator of this server to find out if you have access permissions.
The symbolic link cannot be followed because its type is disabled.
#>

# Enable Remote to remote symlink following if it's not already set
$r2rEnabled=fsutil behavior query SymlinkEvaluation | select-string -Pattern "Remote to remote symbolic links are enabled."
$r2lEnabled=fsutil behavior query SymlinkEvaluation | select-string -Pattern "Remote to local symbolic links are enabled."
if (!($r2rEnabled) -or !($r2lEnabled)){
write-host "`Symbolic links following is disabled.`nEnabling this feature..."
fsutil behavior set SymlinkEvaluation R2R:1;
fsutil behavior set SymlinkEvaluation R2L:1
write-host "`nThis is now the system's settings after the change:"
fsutil behavior query SymlinkEvaluation
}
}


function checkDiskFree{
[cmdletbinding()]
param(
[string]$volume="C:\",
[string]$targetNode="localhost"
)

<#
Excerpt from http://technet.microsoft.com/en-us/library/ee692290(WS.10).aspx:

"For volumes less than 500 megabytes, the minimum is 50 megabytes of free space.
For volumes more than 500 megabytes, the minimum is 320 megabytes of free space.
It is recommended that least 1 gigabyte of free disk space on each volume if the volume size is more than 1 gigabyte."

#>

# Import variables
$thisNode=$targetNode

# Fix targetVolume input if it's missing the suffix
if ($volume -like "*\"){$thisVolume=$volume.Substring(0,$volume.Length-1)}else{$thisVolume=$volume}

# Obtain disk information
$diskObject = Get-WmiObject Win32_LogicalDisk -ComputerName $thisNode -Filter "DeviceID='$thisVolume'"
$diskFree=[Math]::Round($diskObject.FreeSpace / 1MB)
$diskSize=[Math]::Round($diskObject.Size / 1MB)

switch ($diskSize){
{$diskSize -ge 1024} {if ($diskFree -gt 1024){$feasible=$True;}else{$feasible=$False;};;break;}
{$diskSize -ge 500} {if ($diskFree -gt 320){$feasible=$True;}else{$feasible=$False;};;break;}
{$diskSize -lt 500} {if ($diskFree -gt 50){$feasible=$True;}else{$feasible=$False;};break;}
}

return $feasible
}

function removeSnapshots{
[cmdletbinding()]
param(
[int]$daysOlderThan=365
)

# Remove links older than X days
function removeLinks{
[cmdletbinding()]
param(
[string]$snapshotRootDirectory="C:\Snapshots",
[int]$olderThanXDays=365
)
$allSymlinks=Get-ChildItem $snapshotRootDirectory -Recurse -Depth 3 -ErrorAction SilentlyContinue|Where-Object {($_.Attributes -match "ReparsePoint")}

foreach ($link in $allSymLinks){
$creationTime=$link.CreationTime
$thisSymlink=$link.FullName
$removeSymlink=$creationTime -lt (Get-Date).AddDays(-$olderThanXDays)

# Remove symlink if condition is true
if ($removeSymlink){
(Get-Item $thisSymlink).Delete()
"Shadow link $thisSymlink with creation time of $creationTime has been removed."
}else{
"Shadow link $thisSymlink with creation time of $creationTime has NOT been removed."
}
}
}

# Remove old snapshots
function deleteOldSnapshots{
[cmdletbinding()]
param(
[int]$olderThanXDays=365
)
$allSnapshots=Get-WmiObject Win32_Shadowcopy

$allSnapshots | ForEach-Object {
$snapshotDate = $_.InstallDate
$snapshotID = $_.ID
$snapshotDateTimeValue = [management.managementDateTimeConverter]::ToDateTime($snapshotDate)
$thisClientAccessibleValue = $_.ClientAccessible
$currentDate = Get-Date
$timeSpan = New-TimeSpan $snapshotDateTimeValue $currentDate
$days = $timeSpan.Days

If ($days -gt $olderThanXDays -and $thisClientAccessibleValue -eq "True") {
$_.Delete()
"$snapshotID with date stamp of $snapshotDate has been deleted."
} else{
"$snapshotID with date stamp of $snapshotDate has NOT been deleted."
}
}
}

removeLinks -snapshotRootDirectory $localSnapshotPath -olderThanXDays $daysOlderThan
deleteOldSnapshots -olderThanXDays $daysOlderThan
}

function createNewSnapshot{
[cmdletbinding()]
param(
[string]$targetVolume="C:\",
[string]$label=$volumeLabel,
[string]$localShapshot="$localSnapshotPath\$hostname\$volumeLabel\$(Get-Date -Format 'yyyy-MM-dd_hh.mm.ss')"
)

# Create snapshot directory
New-Item -ItemType Directory -Force -Path "$localSnapshotPath\$hostname\$volumeLabel\" | Out-Null;

# Fix targetVolume input if it's missing the suffix
if (!($targetVolume -like "*\")){$targetVolume+="\"}

# Create the VSS snapshot
$shadowCopyClass=[WMICLASS]"root\cimv2:win32_shadowcopy";
$thisSnapshot = $shadowCopyClass.Create($targetVolume, "ClientAccessible");
$thisShadow = Get-WmiObject Win32_ShadowCopy | Where-Object { $_.ID -eq $thisSnapshot.ShadowID };
$thisShadowPath = $thisShadow.DeviceObject + "\";

# Make links to this snapshot
#cd $snapshotPath | out-null
#cmd /c mklink /d $localShapshot $thisShadowPath;
cmd /c mklink /J $localShapshot $thisShadowPath;
<# PowerShell version 5.0 required
+-----------------------+-----------------------------------------------------------+
| mklink syntax | Powershell equivalent |
+-----------------------+-----------------------------------------------------------+
| mklink Link Target | New-Item -ItemType SymbolicLink -Name Link -Target Target |
| mklink /D Link Target | New-Item -ItemType SymbolicLink -Name Link -Target Target |
| mklink /H Link Target | New-Item -ItemType HardLink -Name Link -Target Target |
| mklink /J Link Target | New-Item -ItemType Junction -Name Link -Target Target |
+-----------------------+-----------------------------------------------------------+

SymbolicLink (modern) supports UNC paths, while Junction (older) does not.
#>
"Snapshot of $targetVolume has been made and it's accessible at this path: $localShapshot"

# Export variables that this specific snapshot can be targeted and removed
#$GLOBAL:shadow=$thisShadow;
#$GLOBAL:snapshot=$thisSnapshot;
}

function proceed{
$GLOBAL:localSnapshotPath="$localSnapshotDirectory"
New-Item -ItemType Directory -Force -Path $localSnapshotPath | Out-Null;
enableR2RSymbolicLinks;

removeSnapshots -daysOlderThan $retentionPeriod;

if ((get-item $localSnapshotPath) -and ($localVolumes)){
$localVolumes|%{
$GLOBAL:volumeLetter="$_"[0];
$GLOBAL:volumelabel="Volume_$volumeLetter`_$((get-volume -DriveLetter $volumeLetter).FileSystemLabel)";
if (!($volumeLabel)){$volumelabel="Volume_$volumeLetter"};
$snapshotLink="$localSnapshotPath\$hostname\$volumeLabel\$(Get-Date -Format 'yyyy-MM-dd_hh.mm.ss')";

if(checkDiskFree -volume $_){
createNewSnapShot -targetVolume $_ -label $volumeLabel -localShapshot $snapshotLink;
}else{"Volume $_ does NOT have sufficient disk space available for taking snapshots."}
}

}else{"Program aborted due to insufficient local volumes."}
}

proceed;

PowerShell: Check Servers on Domain to Locate A Domain Account Being Set to Run Services and Scheduled Tasks

# Obtain the name of the default domain Administrator account (this account is expected to be disabled)
$domain=(net config workstation) -match 'Workstation domain\s+\S+$' -replace '.+?(\S+)$','$1';
$defaultDomainAdmin="$domain\Administrator"

# Regex key to search for server names: look back to CN= as $0, match anything but a comma as $1, capture CN|OU as $2,separated by =, capture next part as $3
$regexServers="(?<=CN=)(.*?),(OU|CN)=([^,]+)"

# Collect server names and their containers
$servers=dsquery * -Filter "(&(objectCategory=computer)(operatingSystem=*server*))" -limit 1000000| %{[void]($_ -match $regexServers);$matches[1];$matches[3]}|select @{Name="Name";expression={$matches[1]}},@{Name="container";expression={$matches[3]}}|sort-object -Property Name -Unique

Function checkServiceAccount($searchAccount,$sherver){

# Original function is meant to query multiple machines. It's now adapted to just one machine
$servers=$sherver
$runas=$searchAccount
$scheduledTasksMatch="";
$servicesMatch="";
$result="";

Function Search-ScheduledTasks{
Param(
[array]$ComputerNames = $servers, #Accepts input and cast it as an array
[string]$runasUser=$runas
)
Begin{
$Results = @() #Initializes an empty array
}
Process{
If ($_){ #Checks if this function is being called via pipe command. If so, use set $ComputerNames variable as pipe
$ComputerNames = $_
}
ForEach ($Computer in $ComputerNames){
If (Test-Connection $Computer -Quiet){ #Checks for connectivity before proceeding
# Use the legacy schtasks command from localhost to query remote machine and format an output int CSV format
$tasksAsCSV = schtasks.exe /query /s $Computer /V /FO CSV

# Process the CSV result into PowerShell. Filter entries that are not labeled as "TaskName" and by "Run as User" field
$result = $tasksAsCSV | ConvertFrom-Csv | Where { $_.TaskName -ne "TaskName" -and $_."Run As User" -eq $runasUser}

#Appends this result into array collection named results.
$Results += $result
}
} #end foreach
}
End{
if ($Results){
Return $Results."Task To Run"
}
else {
"No Scheduled Events were found for user $runasUser.";
}
}
} #end Search-ScheduledTasks function

$servicesMatch=(Get-Wmiobject win32_service -ComputerName $sherver | where-object{$_.startname -like $runasUser}|select @{name="ServiceName";expression={$_.name}}).ServiceName|out-string
$scheduledTasksMatch = Search-ScheduledTasks;

if($servicesMatch){$result+=("$servicesMatch").Trim()}else{"No services are currently using the $searchAccount."}
if($scheduledTasksMatch){$result+="`n$scheduledTasksMatch"}
return $result;
}

$servers | %{checkServiceAccount $defaultDomainAdmin $_}

PowerShell: Windows Servers Discovery

<# Servers-Discovery.ps1
Version: 0.02

Purpose: to generate a CSV spreadsheet with information about servers on the domain
1. Query Active Directory for a list of server names
2. Probe each server for its general information (IPs, Machine Type, CPU, Memory, Storage, Last Update, Antivirus, etc.)
3. Detect potential security vulnerabilities
- List local admin accounts
- Detect presence of LAPS
- Detect known exploitable protocol level vulnerabilities
- Detect common vulnerabilities

Requirements:
1. Remote Windows machines must have WinRM or WMI RPC Service enabled
2. Network access to the servers subnet from the jump box is assumed

Future development:
1. Display the host name if node is a virtual machine
a. Hyper-V: (done)
b. VMware: getVmwareHostname $guestVMName (code to be written)
2. Label machineTypes correctly: VMware (done), Hyper-V (done), AWS, Azure, Google, etc.
3. Remotely enable WinRM on machines that does not have it enabled (done, but not 100% effective)
4. Optimize the code so that all queries would be processed in one pass per node, instead of having to run multiple queries to assign various variables
5. Run in the context of a domain administrator
6. Perform a server subnets scan and resolve all servernames, then merge that list with the list obtained from AD.
7. Collect information about network devices
8. Collect information about Linux machines
9. Detect whether the computer objects are Microsoft clustered roles, rather than full Windows instances
10. Deal with Docker containers
#>

# Regex key to search for server names: look back to CN= as $0, match anything but a comma as $1, capture CN|OU as $2,separated by =, capture next part as $3
$regexServers="(?<=CN=)(.*?),(OU|CN)=([^,]+)"

# Collect server names and their containers
$servers=dsquery * -Filter "(&(objectCategory=computer)(operatingSystem=*server*))" -limit 1000000| %{[void]($_ -match $regexServers);$matches[1];$matches[3]}|select @{Name="Name";expression={$matches[1]}},@{Name="container";expression={$matches[3]}}|sort-object -Property Name -Unique

# If this error occurs: "dsquery failed:The specified domain either does not exist or could not be contacted." Then, check default DNS servers on the jump box. Sometimes, VPN connections may inject additional DNS entries that will lead to this run-time error.

# Set CSV export location
$scriptName=$MyInvocation.MyCommand.Path
$scriptPath=Split-Path -Path $scriptName
$csvExport="$scriptPath`\serversDiscovery.csv"

# Init output as an empty array
$GLOBAL:computerObjects=@()

# Init other variables
$hklm = 2147483650
$hyperVHostKey="HKLM:\SOFTWARE\Microsoft\Virtual Machine\Guest\Parameters"
$hyperVHostKeyValue="HostName"
$domain=(net config workstation) -match 'Workstation domain\s+\S+$' -replace '.+?(\S+)$','$1';
$defaultDomainAdmin="$domain\Administrator"

function getVmwareHostname($vmName){
# Install VMWare PowerCLI if it's not available in the system
if(!(Get-Command -Module VMWare*)){
Install-Module -Name VMware.PowerCLI -Scope CurrentUser;
Set-PowerCLIConfiguration -Scope AllUsers -ParticipateInCeip $false -InvalidCertificateAction Ignore
}

# Connect to our vCenter Server using the logged in credentials
$vmwareServerName="192.168.100.100"
Connect-VIServer $vmwareServerName

# Collect Hostname
#$thisVM = Get-VM -Name localhost;
#Get-VMHost -VM $thisVM
$host=(Get-VM -Name $vmName | Select @{N="Host";E={$_.Host.Name}}).Host
}

function getHyperVHostname($guestVMName){
$Hive = [Microsoft.Win32.RegistryHive]::LocalMachine;
$KeyPath = 'SOFTWARE\Microsoft\Virtual Machine\Guest\Parameters';
$Value = 'HostName';
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Hive, $guestVMName);
$key = $reg.OpenSubKey($KeyPath);
return $key.GetValue($Value) ;
}

Function checkServiceAccount($searchAccount,$sherver){

# Original function is meant to query multiple machines. It's now adapted to just one machine
$servers=$sherver
$runas=$searchAccount
$scheduledTasksMatch="";
$servicesMatch="";
$result="";

Function Search-ScheduledTasks{
Param(
[array]$ComputerNames = $servers, #Accepts input and cast it as an array
[string]$runasUser=$runas
)
Begin{
$Results = @() #Initializes an empty array
}
Process{
If ($_){ #Checks if this function is being called via pipe command. If so, use set $ComputerNames variable as pipe
$ComputerNames = $_
}
ForEach ($Computer in $ComputerNames){
If (Test-Connection $Computer -Quiet){ #Checks for connectivity before proceeding
# Use the legacy schtasks command from localhost to query remote machine and format an output int CSV format
$tasksAsCSV = schtasks.exe /query /s $Computer /V /FO CSV

# Process the CSV result into PowerShell. Filter entries that are not labeled as "TaskName" and by "Run as User" field
$result = $tasksAsCSV | ConvertFrom-Csv | Where { $_.TaskName -ne "TaskName" -and $_."Run As User" -eq $runasUser}

#Appends this result into array collection named results.
$Results += $result
}
} #end foreach
}
End{
if ($Results){
Return $Results."Task To Run"
}
else {
"No Scheduled Events were found for user $runasUser.";
}
}
} #end Search-ScheduledTasks function

$servicesMatch=(Get-Wmiobject win32_service -ComputerName $sherver | where-object{$_.startname -like $runasUser}|select @{name="ServiceName";expression={$_.name}}).ServiceName|out-string
$scheduledTasksMatch = Search-ScheduledTasks;

if($servicesMatch){$result+=("$servicesMatch").Trim()}else{"No services are currently using the $searchAccount."}
if($scheduledTasksMatch){$result+="`n$scheduledTasksMatch"}
return $result;
}

# Function obtained from http://ajitgupta.net/2017/08/26/determine-windows-activation-status-with-powershell/
# Author: AJIT GUPTA
function Get-ActivationStatus {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[string]$DNSHostName = $Env:COMPUTERNAME
)
process {
try {
$wpa = Get-WmiObject SoftwareLicensingProduct -ComputerName $DNSHostName `
-Filter "ApplicationID = '55c92734-d682-4d71-983e-d6ec3f16059f'" `
-Property LicenseStatus -ErrorAction Stop
} catch {
$status = New-Object ComponentModel.Win32Exception ($_.Exception.ErrorCode)
$wpa = $null
}
$out = New-Object psobject -Property @{
ComputerName = $DNSHostName;
Status = [string]::Empty;
}
if ($wpa) {
:outer foreach($item in $wpa) {
switch ($item.LicenseStatus) {
0 {$out.Status = "Unlicensed"}
1 {$out.Status = "Licensed"; break outer}
2 {$out.Status = "Out-Of-Box Grace Period"; break outer}
3 {$out.Status = "Out-Of-Tolerance Grace Period"; break outer}
4 {$out.Status = "Non-Genuine Grace Period"; break outer}
5 {$out.Status = "Notification"; break outer}
6 {$out.Status = "Extended Grace"; break outer}
default {$out.Status = "Unknown value"}
}
}
} else {$out.Status = $status.Message}
return $out.Status
}
}

function getLatestWinDefenderVersion{
$regexGetVersion='(<[^>]+>|[:\s]+|Version)' #Test regex at RegexStorm.net
$regexGetReleaseDate='(<[^>]+>|^\s|\s+$|Released:)'
$winDefenderDefURL="https://www.microsoft.com/en-us/wdsi/definitions"
$count=0;
do{
$count++;
try{
$html = Invoke-WebRequest –Uri $winDefenderDefURL -Method Get -UseBasicParsing;
}
catch{

$_.Exception.Message;
$html=$False
}
}
until ($html -or ($count -eq 3))
$GLOBAL:latestVersion=($html.tostring() -split "[`r`n]" | select-string "Version:") -replace $regexGetVersion;
$GLOBAL:releaseDate=($html.tostring() -split "[`r`n]" | select-string "Released:") -replace $regexGetReleaseDate -replace " ";
#$releaseDateLocalTime=[System.TimeZoneInfo]::ConvertTimeFromUtc($releaseDate,[System.TimeZoneInfo]::FindSystemTimeZoneById((Get-WmiObject win32_timezone).StandardName))
}

function checkWinDefender($remoteSherver){
if(!($latestVersion) -or !($releaseDate)){getLatestWinDefenderVersion}
$x=$latestVersion;
$y=$releaseDate;

function localfunc($x,$y){
$latestVersion=$x;
$releaseDate=$y;
try{
$regexGetVersion='(<[^>]+>|[:\s]+|Version)' #Test regex at RegexStorm.net
$regexGetReleaseDate='(<[^>]+>|^\s|\s+$|Released:)'
$mpStatus=Get-MpComputerStatus
$actualVersion=$mpStatus.AntivirusSignatureVersion -replace $regexGetVersion
$lastUpdated=$mpStatus.AntispywareSignatureLastUpdated

$releaseDateLocalTime=[System.TimeZoneInfo]::ConvertTimeFromUtc($releaseDate,[System.TimeZoneInfo]::FindSystemTimeZoneById((Get-WmiObject win32_timezone).StandardName))
$timeDifference = New-TimeSpan -Start $lastUpdated -End $releaseDate
$daysDifference=$timeDifference.Days

"It has been $daysDifference day(s) between Last Update: $lastUpdated and New Release: $releaseDate";
if ($actualVersion -ne $latestVersion){
return "Malware Antivirus Signature version $actualVersion on this system does not match the Latest version of $latestVersion";
#Update-MPSignature;
}
else{
return "Excellent! Malware Antivirus Definition $actualVersion on this computer matches the Microsoft release.";
}
return "Please note that Microsoft Windows Defender should be disabled if there's an Enterprise Antivirus scanner installed on this system.";
}
catch{
return "Windows Defender is NOT enabled on this system.";
}
}

$result=Invoke-Command -ComputerName $remoteSherver -ScriptBlock {
param( $importedFunc,$x,$y)
[ScriptBlock]::Create($importedFunc).Invoke($x,$y)
} -ArgumentList ${function:localfunc},$x,$y

return $result;
}

function checkRDP($remoteSherver){
function localfunc{
$rdpAuth=(Get-WmiObject -class "Win32_TSGeneralSetting" -Namespace root\cimv2\terminalservices -Filter "TerminalName='RDP-tcp'").UserAuthenticationRequired
$encryptionLevel=(Get-WmiObject -class "Win32_TSGeneralSetting" -Namespace root\cimv2\terminalservices -Filter "TerminalName='RDP-tcp'").MinEncryptionLevel
switch ($encryptionLevel){
1 {$compliant="Low";}
2 {$compliant="Client Compatible";}
3 {$compliant="High";}
4 {$compliant="FIPS Compliant";}
}
if($rdpAuth){return "RDP Network Authentication Requirement: passed!`nRDP Encryption Level: $compliant";}else{return "RDP Network Authentication Requirement: Fail";}
}

$result=Invoke-Command -ComputerName $remoteSherver -ScriptBlock {
param( $importedFunc)
[ScriptBlock]::Create($importedFunc).Invoke()
} -ArgumentList ${function:localfunc}

return $result;
}

function checkSpectreVulnerability($remoteSherver){
function localfunc{
$regexOctets="([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)"
$patchedVersion="10.0.14393.2842"
$actualVersion=(Get-Item C:\Windows\system32\mcupdate_genuineintel.dll | select VersionInfo).VersionInfo.ProductVersion

$actualVersion -match $regexOctets;
[string]$a1=$matches[1].PadLeft(3,'0');
[string]$a2=$matches[2].PadLeft(8,'0');
[string]$a3=$matches[3].PadLeft(8,'0');
[string]$a4=$matches[4].PadLeft(8,'0');
[single]$a=$a1+$a2+$a3+$a4

$patchedVersion -match $regexOctets;
[string]$p1=$matches[1].PadLeft(3,'0');
[string]$p2=$matches[2].PadLeft(8,'0');
[string]$p3=$matches[3].PadLeft(8,'0');
[string]$p4=$matches[4].PadLeft(8,'0');
[single]$p=$p1+$p2+$p3+$p4

"System version: $actualVersion VS minimum version required: $patchedVersion"

if($a -ge $p){return "Spectre meltdown vulnerabilities: Pass";}else{return "Spectre meltdown vulnerabilities: Fail";}
}

$result=Invoke-Command -ComputerName $remoteSherver -ScriptBlock {
param( $importedFunc)
[ScriptBlock]::Create($importedFunc).Invoke()
} -ArgumentList ${function:localfunc}

return $result;
}

function checkOtherVulnerabilities($remoteSherver){
function localfunc{
$result="";

# Checking IE
$ieKeys=@(
@("CVE-2017-829 (32-Bit)","HKLM:SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_PRINT_INFO_DISCLOSURE_FIX"),
@("CVE-2017-8529 (64-bit)","HKLM:SOFTWARE\WOW6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_PRINT_INFO_DISCLOSURE_FIX"),
@("ASLR Hardening Setting for IE (32-Bit)","HKLM:SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ALLOW_USER32_EXCEPTION_HANDLER_HARDENING"),
@("ASLR Hardening Setting for IE (64-Bit)","HKLM:SOFTWARE\WOW6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ALLOW_USER32_EXCEPTION_HANDLER_HARDENING")
)
$result+="Internet Explorer: "
foreach ($ieKey in $ieKeys){
try{
$value=(Get-ItemProperty -Path $ieKey[1] -Name "iexplore.exe" -ErrorAction SilentlyContinue).'iexplore.exe';
}
catch{
$value=0;
continue;
}
$ieResult=if($value){"Pass"}else{"Fail";}
$ieKey[0] + ": " + $ieResult
}
$result+="$ieResult";

# Checking Memory Management
$memKeys=@(
@("CVE-2017-5715","HKLM:SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management","FeatureSettingsOverride","0"),
@("CVE-2017-5715","HKLM:SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management","FeatureSettingsOverrideMask","3"),
@("CVE-2017-5753-54","HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization","MinVmVersionForCpuBasedMitigations","1.0")
)
$result+="`nMemory Management Registry Keys: "
foreach ($memKey in $memKeys){
$value=(Get-ItemProperty -Path $memKey[1] -Name $memKey[2] -ErrorAction SilentlyContinue).[string]($memKey[2])
$memResult=if($value -eq $memKey[3]){"Pass"}else{"Fail";}
$memKey[0]+ ": " + $memResult;
}
$result+="$memResult";

# Checking Remote Code Execution
$minVersion=14
$vcVersions=(Get-ItemProperty Registry::HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* -ErrorAction SilentlyContinue| where {$_.displayname -like "Microsoft Visual C++*"} | Select-Object DisplayVersion)
foreach ($version in $vcVersions){
if($version.DisplayVersion -ge $minVersion){
$safeFlag=$True;
}
}
if ($safeFlag){
$result+="`nMS11-025 (MFC Remote Code Execution): Pass"
}
else{$result+="`nMS11-025 (MFC Remote Code Execution): Fail"}

# Checking unquoted service path enumeration
$unquotedServicePathItems=(wmic service get name","displayname","pathname","startmode |findstr /i "auto" |findstr /i /v "c:\windows\\" |findstr /i /v "''").Trim()
if ($unquotedServicePathItems){$result+="`nUnquoted Service Path Enumeration Vulnerabilities: Fail."}else{$result+="`nUnquoted Service Path Enumeration Vulnerabilities: Passed."}

# Checking LAPS
if(Get-ChildItem 'C:\Program Files\LAPS\CSE\Admpwd.dll' -ErrorAction SilentlyContinue){$result+="`nLAPS is installed."}else{$result+="`nLAPS is not installed.";}

# Checking SNMP
try{
$permittedManagers=Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\services\SNMP\Parameters\PermittedManagers" -ErrorAction SilentlyContinue;
$validCommunities=Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\services\SNMP\Parameters\ValidCommunities" -ErrorAction SilentlyContinue;
}
finally{
if (($permittedManagers) -and ($validCommunities)){
$result+="`nSNMP: permittedManagers and validCommunities values are detected.";
}else{
$result+="`nSNMP: permittedManagers and validCommunities values are NOT detected.";
}
}

# Checking IISCrypto
$iisServer=Get-Service -Name 'IISADMIN' -ErrorAction SilentlyContinue | Select -ExpandProperty Status
if($iisServer){
$result+="`nIIS is detected on this system with status $iisServer"
$regHiveSSL30="Registry::HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Server"
$regHiveTLS10="Registry::HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server"
$regHiveTLS11="Registry::HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Server"
$regHiveTLS12="Registry::HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server"
$isSSL30Enabled = Get-ItemProperty -Path $regHiveSSL30 -Name "Enabled" -ErrorAction SilentlyContinue
$isTLS10Enabled = Get-ItemProperty -Path $regHiveTLS10 -Name "Enabled" -ErrorAction SilentlyContinue
$isTLS11Enabled = Get-ItemProperty -Path $regHiveTLS11 -Name "Enabled" -ErrorAction SilentlyContinue
$isTLS12Enabled = Get-ItemProperty -Path $regHiveTLS12 -Name "Enabled" -ErrorAction SilentlyContinue
If ($isSSL30Enabled) {$result+="`nSSL 3.0 is Enabled.";} ElseIf ($isSSL30Enabled -eq 0){$result+="`nSSL 3.0 is Disabled.";} else {$result+="`nSSL 3.0 SCHANNEL is not detected.";}
If ($isTLS10Enabled) {$result+="`nTLS 1.0 is Enabled.";} ElseIf ($isTLS10Enabled -eq 0){$result+="`nTLS 1.0 is Disabled.";} else {$result+="`nTLS 1.0 SCHANNEL is not detected.";}
If ($isTLS11Enabled) {$result+="`nTLS 1.1 is Enabled.";} ElseIf ($isTLS11Enabled -eq 0){$result+="`nTLS 1.1 is Disabled.";} else {$result+="`nTLS 1.2 SCHANNEL is not detected.";}
If ($isTLS12Enabled) {$result+="`nTLS 1.2 is Enabled.";} ElseIf ($isTLS12Enabled -eq 0){$result+="`nTLS 1.2 is Disabled.";} else {$result+="`nTLS 1.2 SCHANNEL is not detected.";}
#If([System.IO.File]::Exists("C:\Windows\IISCryptoCli.exe")){$result+="`nIISCryptoCli is available."}else{$result+="`nIISCryptoCli is not available at C:\Windows.";}
if((Get-Command IISCryptoCli.exe -ErrorAction SilentlyContinue) -or (Get-Command IISCrypto.exe -ErrorAction SilentlyContinue)){$result+="`nIIS Crypto is available on this system."}else{$result+="`nIIS Crypto is not available on this system.";}
}

return $result;
}

$otherVulnerabilities=Invoke-Command -ComputerName $remoteSherver -ScriptBlock {
param( $importedFunc)
[ScriptBlock]::Create($importedFunc).Invoke()
} -ArgumentList ${function:localfunc}

return $otherVulnerabilities;
}

function getlocalAdministrators($remoteSherver){
invoke-command -computername $remoteSherver {net localgroup administrators | where {$_ -AND $_ -notmatch "command completed successfully"} | select -skip 4}
}

function checkServer($name){
$computerObject=New-Object PSObject
$ips="Unknown";
$machineType="Unknown";
$machineModel="Unknown";
$os="Unknown";
$cpu=$cpuLoad="Unknown";
$memory="Unknown";
$volumes="Unknown";
$lastUpdate="Unknown";
$antivirusName="Unknown";
$activationStatus="Unknown";
$serial="Unknown";
$cpuObject=@();
$servicesWithDefaultDomainAdminUsage="";
$winDefenderStatus="";
$rdpSecurity="";
$spectreVulnerability="";
$otherVulnerabilities="";
$localAdministrators="";
try{
$ips=([System.Net.Dns]::GetHostAddresses($name)|?{$_.AddressFamily -eq "InterNetwork"}|%{$_.IPAddressToString;}|out-string).Trim();
$machineModel=(Get-WmiObject -Class Win32_ComputerSystem -ComputerName $name -ea stop).Model
$cpuObject = Get-WmiObject -computername $name win32_processor -ea stop |select Name,NumberOfCores,LoadPercentage
$cpu=($cpuObject|select Name,NumberOfCores|Out-String).Trim()
$cpuLoad = ($cpuObject| Measure-Object -property LoadPercentage -Average | Select @{Name="CurrentLoad";Expression={"{0:N2} %" -f ($_.Average)}}).CurrentLoad
$osAndMemory = gwmi -Class win32_operatingsystem -computername $name -ea stop| select @{Name="os";Expression={$_.Caption}},@{Name="Memory";Expression={"{0:N2} GB" -f ($_.TotalVisibleMemorySize / 1048576)}},@{Name = "Utilization"; Expression = {"{0:N2} %" -f ((($_.TotalVisibleMemorySize - $_.FreePhysicalMemory)*100)/ $_.TotalVisibleMemorySize) }}
if($osAndMemory){
$os=$osAndMemory.os;
$memory=$osAndMemory.Memory;
$memoryUtilization=$osAndMemory.Utilization;
}
$volumes = (gwmi -Class win32_volume -computername $name -Filter "DriveType!=5" -ea stop| ?{$_.DriveLetter -ne $isnull}|Select-object @{Name="Letter";Expression={$_.DriveLetter}},@{Name="Capacity";Expression={"{0:N2} GiB" -f ($_.Capacity/1073741824)}},@{Name = "Utilization"; Expression = {"{0:N2} %" -f ((($_.Capacity-$_.FreeSpace) / $_.Capacity)*100) } } | Out-String).Trim()
$activationStatus=Get-ActivationStatus $name
$serial=(Get-WMIObject -Class Win32_BIOS -ComputerName $name).SerialNumber
#[string]$lastUpdate=gwmi win32_quickfixengineering -ComputerName $name |select HotFixID,InstalledOn|Select-Object -first 1
$lastUpdate=(Get-HotFix -ComputerName $name | Measure-Object InstalledOn -Maximum).Maximum.ToString().Trim()
$antivirusName=((get-wmiobject -class "Win32_Process" -namespace "root\cimv2" -ComputerName $name | where-object {$_.Name.ToLower() -match "antivirus|endpoint|protection|security|defender|msmpeng"}).Name | Out-String).Trim()
$servicesWithDefaultDomainAdminUsage=(checkServiceAccount $defaultDomainAdmin $name|Out-String).Trim()
if ($winRmAvailable){
$localAdministrators=(getlocalAdministrators $name|Out-String).Trim()
$winDefenderStatus=(checkWinDefender $name|Out-String).Trim()
$rdpSecurity=checkRDP $name
$spectreVulnerability=(checkSpectreVulnerability $name|Out-String).Trim()
$otherVulnerabilities=(checkOtherVulnerabilities $name|Out-String).Trim();
}else{
$localAdministrators=$otherVulnerabilities=$winDefenderStatus=$rdpSecurity=$spectreVulnerability="Unable to check due to WinRM connection errors."
}
}
catch{
if(!($machineModel)){$machineModel="Unknown"};
if(!($machineType)){$machineType="Unknown"};
if(!($osAndMemory)){$os=$memory=$memoryUtilization="Unknown";}
if(!($ips)){$ips="Unknown";}
if(!($cpuObject)){$cpu=$cpuLoad="Unknown";}
if(!($volumes)){$volumes="Unknown";}
if(!($serial)){$serial="Unknown"}
Continue;
}
finally{
switch -wildcard ($machineModel){
"VMware*" {$machineType="VMWare Virtual Machine";$hostname="N/A";}
"Virtual Machine" {$machineType="Hyper-V Virtual Machine";$hostname=getHyperVHostname $name}
"*HVM*" {$machineType="AWS Virtual Machine";$hostname="N/A";}
"*.*" {$machineType="AWS Virtual Machine";$hostname="N/A";}
"Unknown" {$machineType="Unknown";$hostname="Unknown";}
default {if (validateAzureVM $name){$machineType="Azure Virtual Machine"}else{$machineType="Physical Machine";$hostname="N/A"};}
}
$computerObject | Add-Member -MemberType NoteProperty -Name "Name" -Value $name
$computerObject | Add-Member -MemberType NoteProperty -Name "ips" -Value $ips
$computerObject | Add-Member -MemberType NoteProperty -Name "machineType" -Value $machineType
$computerObject | Add-Member -MemberType NoteProperty -Name "hostname" $hostname
$computerObject | Add-Member -MemberType NoteProperty -Name "machineModel" -Value $machineModel
$computerObject | Add-Member -MemberType NoteProperty -Name "os" -Value $os
$computerObject | Add-Member -MemberType NoteProperty -Name "activationStatus" -Value $activationStatus
$computerObject | Add-Member -MemberType NoteProperty -Name "serial" -Value $serial
$computerObject | Add-Member -MemberType NoteProperty -Name "cpu" -Value $cpu
$computerObject | Add-Member -MemberType NoteProperty -Name "cpuUtilization" -Value $cpuLoad
$computerObject | Add-Member -MemberType NoteProperty -Name "memory" -Value $memory
$computerObject | Add-Member -MemberType NoteProperty -Name "memoryUtilization" -Value $memoryUtilization
$computerObject | Add-Member -MemberType NoteProperty -Name "volumes" -Value $volumes
$computerObject | Add-Member -MemberType NoteProperty -Name "container" -Value $container
$computerObject | Add-Member -MemberType NoteProperty -Name "lastUpdate" -Value $lastUpdate
$computerObject | Add-Member -MemberType NoteProperty -Name "antivirus" -Value $antivirusName
$computerObject | Add-Member -MemberType NoteProperty -Name "localAdministrators" -Value $localAdministrators
$computerObject | Add-Member -MemberType NoteProperty -Name "servicesWithDefaultDomainAdminUsage" -Value $servicesWithDefaultDomainAdminUsage
$computerObject | Add-Member -MemberType NoteProperty -Name "winDefenderStatus" -Value $winDefenderStatus
$computerObject | Add-Member -MemberType NoteProperty -Name "rdpSecurity" -Value $rdpSecurity
$computerObject | Add-Member -MemberType NoteProperty -Name "spectreVulnerability" -Value $spectreVulnerability
$computerObject | Add-Member -MemberType NoteProperty -Name "otherVulnerabilities" -Value $otherVulnerabilities
}
$computerObject;
$GLOBAL:computerObjects+=,$computerObject
}

# Function obtained from https://gallery.technet.microsoft.com/scriptcenter/Detect-Windows-Azure-aed06d51
# snippet author: Craig Landis
function validateAzureVM($instance){

Function Confirm-AzureVM {

$source = @"
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Net.NetworkInformation;

namespace Microsoft.WindowsAzure.Internal
{
/// <summary>
/// A simple DHCP client.
/// </summary>
public class DhcpClient : IDisposable
{
public DhcpClient()
{
uint version;
int err = NativeMethods.DhcpCApiInitialize(out version);
if (err != 0)
throw new Win32Exception(err);
}

public void Dispose()
{
NativeMethods.DhcpCApiCleanup();
}

/// <summary>
/// Gets the available interfaces that are enabled for DHCP.
/// </summary>
/// <remarks>
/// The operational status of the interface is not assessed.
/// </remarks>
/// <returns></returns>
public static IEnumerable<NetworkInterface> GetDhcpInterfaces()
{
foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces())
{
if (nic.NetworkInterfaceType != NetworkInterfaceType.Ethernet) continue;
if (!nic.Supports(NetworkInterfaceComponent.IPv4)) continue;
IPInterfaceProperties props = nic.GetIPProperties();
if (props == null) continue;
IPv4InterfaceProperties v4props = props.GetIPv4Properties();
if (v4props == null) continue;
if (!v4props.IsDhcpEnabled) continue;

yield return nic;
}
}

/// <summary>
/// Requests DHCP parameter data.
/// </summary>
/// <remarks>
/// Windows serves the data from a cache when possible.
/// With persistent requests, the option is obtained during boot-time DHCP negotiation.
/// </remarks>
/// <param name="optionId">the option to obtain.</param>
/// <param name="isVendorSpecific">indicates whether the option is vendor-specific.</param>
/// <param name="persistent">indicates whether the request should be persistent.</param>
/// <returns></returns>
public byte[] DhcpRequestParams(string adapterName, uint optionId)
{
uint bufferSize = 1024;
Retry:
IntPtr buffer = Marshal.AllocHGlobal((int)bufferSize);
try
{
NativeMethods.DHCPCAPI_PARAMS_ARRAY sendParams = new NativeMethods.DHCPCAPI_PARAMS_ARRAY();
sendParams.nParams = 0;
sendParams.Params = IntPtr.Zero;

NativeMethods.DHCPCAPI_PARAMS recv = new NativeMethods.DHCPCAPI_PARAMS();
recv.Flags = 0x0;
recv.OptionId = optionId;
recv.IsVendor = false;
recv.Data = IntPtr.Zero;
recv.nBytesData = 0;

IntPtr recdParamsPtr = Marshal.AllocHGlobal(Marshal.SizeOf(recv));
try
{
Marshal.StructureToPtr(recv, recdParamsPtr, false);

NativeMethods.DHCPCAPI_PARAMS_ARRAY recdParams = new NativeMethods.DHCPCAPI_PARAMS_ARRAY();
recdParams.nParams = 1;
recdParams.Params = recdParamsPtr;

NativeMethods.DhcpRequestFlags flags = NativeMethods.DhcpRequestFlags.DHCPCAPI_REQUEST_SYNCHRONOUS;

int err = NativeMethods.DhcpRequestParams(
flags,
IntPtr.Zero,
adapterName,
IntPtr.Zero,
sendParams,
recdParams,
buffer,
ref bufferSize,
null);

if (err == NativeMethods.ERROR_MORE_DATA)
{
bufferSize *= 2;
goto Retry;
}

if (err != 0)
throw new Win32Exception(err);

recv = (NativeMethods.DHCPCAPI_PARAMS)
Marshal.PtrToStructure(recdParamsPtr, typeof(NativeMethods.DHCPCAPI_PARAMS));

if (recv.Data == IntPtr.Zero)
return null;

byte[] data = new byte[recv.nBytesData];
Marshal.Copy(recv.Data, data, 0, (int)recv.nBytesData);
return data;
}
finally
{
Marshal.FreeHGlobal(recdParamsPtr);
}
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}

///// <summary>
///// Unregisters a persistent request.
///// </summary>
//public void DhcpUndoRequestParams()
//{
// int err = NativeMethods.DhcpUndoRequestParams(0, IntPtr.Zero, null, this.ApplicationID);
// if (err != 0)
// throw new Win32Exception(err);
//}

#region Native Methods
}

internal static partial class NativeMethods
{
public const uint ERROR_MORE_DATA = 124;

[DllImport("dhcpcsvc.dll", EntryPoint = "DhcpRequestParams", CharSet = CharSet.Unicode, SetLastError = false)]
public static extern int DhcpRequestParams(
DhcpRequestFlags Flags,
IntPtr Reserved,
string AdapterName,
IntPtr ClassId,
DHCPCAPI_PARAMS_ARRAY SendParams,
DHCPCAPI_PARAMS_ARRAY RecdParams,
IntPtr Buffer,
ref UInt32 pSize,
string RequestIdStr
);

[DllImport("dhcpcsvc.dll", EntryPoint = "DhcpUndoRequestParams", CharSet = CharSet.Unicode, SetLastError = false)]
public static extern int DhcpUndoRequestParams(
uint Flags,
IntPtr Reserved,
string AdapterName,
string RequestIdStr);

[DllImport("dhcpcsvc.dll", EntryPoint = "DhcpCApiInitialize", CharSet = CharSet.Unicode, SetLastError = false)]
public static extern int DhcpCApiInitialize(out uint Version);

[DllImport("dhcpcsvc.dll", EntryPoint = "DhcpCApiCleanup", CharSet = CharSet.Unicode, SetLastError = false)]
public static extern int DhcpCApiCleanup();

[Flags]
public enum DhcpRequestFlags : uint
{
DHCPCAPI_REQUEST_PERSISTENT = 0x01,
DHCPCAPI_REQUEST_SYNCHRONOUS = 0x02,
DHCPCAPI_REQUEST_ASYNCHRONOUS = 0x04,
DHCPCAPI_REQUEST_CANCEL = 0x08,
DHCPCAPI_REQUEST_MASK = 0x0F
}

[StructLayout(LayoutKind.Sequential)]
public struct DHCPCAPI_PARAMS_ARRAY
{
public UInt32 nParams;
public IntPtr Params;
}

[StructLayout(LayoutKind.Sequential)]
public struct DHCPCAPI_PARAMS
{
public UInt32 Flags;
public UInt32 OptionId;
[MarshalAs(UnmanagedType.Bool)]
public bool IsVendor;
public IntPtr Data;
public UInt32 nBytesData;
}
#endregion
}
}
"@

Add-Type -TypeDefinition $source

$detected = $False

[void][System.Reflection.Assembly]::LoadWithPartialName('System.Serviceprocess')

$vmbus = [System.ServiceProcess.ServiceController]::GetDevices() | where {$_.Name -eq 'vmbus'}

If($vmbus.Status -eq 'Running')
{
$client = New-Object Microsoft.WindowsAzure.Internal.DhcpClient
try {
[Microsoft.WindowsAzure.Internal.DhcpClient]::GetDhcpInterfaces() | % {
$val = $client.DhcpRequestParams($_.Id, 245)
if($val -And $val.Length -eq 4) {
$detected = $True
}
}
} finally {
$client.Dispose()
}
}
Write-Output $detected
}

$result=Invoke-Command -ComputerName $instance -ScriptBlock {
param( $importedFunc)

# Import the function from the variable inside parameters
[ScriptBlock]::Create($importedFunc).Invoke()

} -ArgumentList ${function:Confirm-AzureVM}

return $result;
}

function enableWinRm($remoteSherver){
# Enable WinRM Remotely
psexec.exe \\$remoteSherver -s C:\Windows\system32\winrm.cmd qc -quiet;

# Test to see if WinRM is indeed installed
$success=test-netconnection $remoteSherver -CommonTCPPort WINRM -InformationLevel Quiet;

return $success;
}

# function obtained from https://copdips.com/2019/09/fast-tcp-port-check-in-powershell.html
# Author: Xiang ZHU
function Test-Port {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true, HelpMessage = 'Could be suffixed by :Port')]
[String[]]$ComputerName,

[Parameter(HelpMessage = 'Will be ignored if the port is given in the param ComputerName')]
[Int]$Port = 5985,

[Parameter(HelpMessage = 'Timeout in millisecond. Increase the value if you want to test Internet resources.')]
[Int]$Timeout = 1000
)

begin {
$result = [System.Collections.ArrayList]::new()
}

process {
foreach ($originalComputerName in $ComputerName) {
$remoteInfo = $originalComputerName.Split(":")
if ($remoteInfo.count -eq 1) {
# In case $ComputerName in the form of 'host'
$remoteHostname = $originalComputerName
$remotePort = $Port
} elseif ($remoteInfo.count -eq 2) {
# In case $ComputerName in the form of 'host:port',
# we often get host and port to check in this form.
$remoteHostname = $remoteInfo[0]
$remotePort = $remoteInfo[1]
} else {
$msg = "Got unknown format for the parameter ComputerName: " `
+ "[$originalComputerName]. " `
+ "The allowed formats is [hostname] or [hostname:port]."
Write-Error $msg
return
}

$tcpClient = New-Object System.Net.Sockets.TcpClient
$portOpened = $tcpClient.ConnectAsync($remoteHostname, $remotePort).Wait($Timeout)

$null = $result.Add([PSCustomObject]@{
RemoteHostname = $remoteHostname
RemotePort = $remotePort
PortOpened = $portOpened
TimeoutInMillisecond = $Timeout
SourceHostname = $env:COMPUTERNAME
OriginalComputerName = $originalComputerName
})
}
}

end {
return $result
}
}

function systemsDiscovery{

foreach($server in $servers){
$serverName=$server.Name
"Scanning $serverName ..."
$GLOBAL:container=$server.container
$GLOBAL:winRmAvailable=Test-Port -ComputerName $serverName -ErrorAction SilentlyContinue
if(!($winRmAvailable)){$GLOBAL:winRmAvailable=enableWinRm $serverName}
checkServer $serverName
}
$computerObjects|Export-Csv -Path $csvExport -NoTypeInformation
"Results have now been saved at: $csvExport"
}

systemsDiscovery;

How to Implement Local Administrator Password Solution (LAPS) on Windows

Overview

LAPS or Local Administrator Password Management is a good solution for local administrator account and password management.

Use-Case:
-We recently had an issue with not being able to login as local administrator on a restored backup of a server in Veeam because we did not know the password for the local admin account.
-This exposed the issue that we need centralized management of Local Administrator Accounts and Passwords.
Action Plan

Basic steps can be broken down into 7 steps:

1. Download LAPS.
2. Extend AD Schema
3. Install LAPS Group Policy Files
4. Set Options for LAPS
5. Assign Permissions
6. Push Group Policy to Appropriate OU’s
7. Validation of Installation and Configuration

1. Preparations

  1. Make a full backup of Active Directory
  2. Download LAPS
    https://www.microsoft.com/en-us/download/details.aspx?id=46899

2. Extend AD Schema (requires Schema Admins membership)

Import-module AdmPwd.PS
Update-AdmPwdADSchema

3. Install LAPS Group Policy Files
– *.admx goes into the “windows\policydefintions” folder
– *.adml goes into the “\windows\policydefinitions\[language]” folder

4. Set options
– GPMC > Computer configuration > Policies > Administrative Templates > LAPS
– Password settings — set frequency and complexity (use default)
– Name of administrator account to manage — Rename administrator account to something non-default such as “localadmin”
– Do not allow password expiration time longer than required by policy — set to TRUE to avoid disconnected computers to change password and be out of sync with AD
– Enable local password management — must be set to TRUE to activate policy

5. Assign Permissions
– Create 2 security groups: “LAPS_ReadOnly” and “LAPS_Admins”
– Create a test OU named “Test_OU” and move some test machines into this container
– Apply GP:

Set-AdmPwdComputerSelfPermission -OrgUnit "Test_OU"

– Set permission:

Set-AdmPwdReadPasswordPermission -OrgUnit "Test_OU" -AllowedPrincipals "LAPS_ReadOnly"
Set-AdmPwdResetPasswordPermission -OrgUnit "Test_OU" -AllowedPrincipals "LAPS_Admins"

6. Push GP to Appropriate OUs
– Script content:

# This script installs the LAPS library onto the local machine
$admpwdFile="\\FileSherver01\Software\LAPS\admpwd.dll"
copy $admpwdFile "%windir%\system32"
regsvr32.exe AdmPwd.dll
gpupdate /force

– Apply this logon script to the correct OU

7. Validation
– Install the LAPS GUI onto a workstation > query a machine within the affected OU > verify that its local admin password has been set

Impacts Assessment:

Users Impacts: None shall be perceived

Systems Internal Impacts:

New Objects:
– 2 new security groups are added: “LAPS_ReadOnly” and “LAPS_Admins”
– 1 new OU named “Test_OU” is created
– 1 new Group Policy named “LAPS” will be made

Logon Script named “admpwdFile.bat” will be applied toward computers located inside the “TEST_OU”:
# This script installs the LAPS library onto the local machine
$admpwdFile=”\\FileSherver01\Software\LAPS\admpwd.dll”
copy $admpwdFile “%windir%\system32”
regsvr32.exe AdmPwd.dll
gpupdate /force

Effects of “Update-AdmPwdADSchema” command:
– AD Schema will be extended 2 new custom attributes:
— cn=ms-Mcs-AdmPwd, CN=Schema,CN=configuration,DC=intranet,DC=kimconnect,DC=com
— cn=ms-Mcs-AdmPwdExpirationTime,CN=Schema,CN=configuration,DC=intranet,DC=kimconnect,DC=com
– This 1 existing Schema Class will be modified
— cn=computer,CN=Schema,CN=Configuration,DC=intranet,DC=kimconnect,DC=com
– Test machines and subsequent machines with GP applied will have this DLL added to local paths of C:\Windows\System32 — file name: admpwd.dll

Risks Analysis:

This is a MEDIUM risk item due to these considerations
– Custom attribute of AD Schema is intended to be permanent; normally, a change to AD schema is a “High” risk concern. Since we are utilizing a Microsoft product to extend an existing Microsoft product, perfect integration is expected. Therefore the risk should be downgraded to “Medium.”
– Domain Admins will have access to the local passwords of all targeted machines. Hence, further AAA mitigation techniques should follow. Hence, this sub-item indicates a “Medium” level consideration.
– No user-perceivable effects. Hence, this sub-item indicates “Low” risk.

Validation:

– “Step 7” in the execution plan provides validation on whether LAPS has been successfully applied toward test Windows machines inside the “TEST_OU”

Roll-back Plan:

-Perform authoritative restore on PDC

Changing SMB Caching on Windows

# To turn OFF caching
$rolesWithNoCaching="SHERVER01","SHERVER02"
$rolesWithNoCaching | %{$shares=Get-SMBShare -scopename "$_"; foreach ($share in $shares){if(!($share.Name -like "*$")){"Turning caching OFF for share: $share.Name..."; Set-SMBShare -Name $share.Name -CachingMode None -Confirm:$False;}};}

# To turn ON caching
$rolesWithCaching="SHERVER03","SHERVER04"
$rolesWithCaching | %{$shares=Get-SMBShare -scopename "$_"; foreach ($share in $shares){if(!($share.Name -like "*$")){"Turning caching ON for share: $share.Name..."; Set-SMBShare -Name $share.Name -CachingMode Manual -Confirm:$False;}};}

# Changing caching mode for a single share
$shareName="SHARE007"
Set-SMBShare -Name $shareName -CachingMode None -Confirm:$False;
Set-SMBShare -Name $shareName -CachingMode Manual -Confirm:$False;

# Legend:
-- None: Prevents users from storing documents and programs offline.
-- Manual: Allows users to identify the documents and programs that they want to store offline. Equivalent to "Allow Caching of Shares"
-- Programs: Automatically stores documents and programs offline.
-- Documents: Automatically stores documents offline.
-- BranchCache: Enables BranchCache and manual caching of documents on the
# Changing cache settings for Windows 2008R2 or older:
# Set DC Name
$dcSherver="DC01"

# All Files and Programs that users open from the shared folder are automatically available offline. Also, Optimized for performance is enabled.
([WMIClass]"\\$dcSherver`\root\cimv2:win32_Process").Create("cmd /C net share PS /Cache:Programs")

# Only the files and programs that users specify are available offline.
([WMIClass]"\\$dcSherver`\root\cimv2:win32_Process").Create("cmd /C net share PS /Cache:Manual")

# All Files and Programs that users open from the shared folder are automatically available offline. Optimized for performance is not checked.
([WMIClass]"\\$dcSherver`\root\cimv2:win32_Process").Create("cmd /C net share PS /Cache:Documents")

# No files or programs from the shared folder are available offline.
([WMIClass]"\\$dcSherver`\root\cimv2:win32_Process").Create("cmd /C net share PS /Cache:None")

Updating SSL Certificates on Active Directory Federation Services (ADFS) Server

1. Adding Tool to enable AD-FS claims visbility:

# Add ClaimsXray (https://adfshelp.microsoft.com/ClaimsXray/TokenRequest)
$authzRules = "=>issue(Type = `"http://schemas.microsoft.com/authorization/claims/permit`", Value = `"true`"); "
$issuanceRules = "@RuleName = `"Issue all claims`"`nx:[]=>issue(claim = x); "
$redirectUrl = "https://adfshelp.microsoft.com/ClaimsXray/TokenResponse"
$samlEndpoint = New-AdfsSamlEndpoint -Binding POST -Protocol SAMLAssertionConsumer -Uri $redirectUrl
Add-ADFSRelyingPartyTrust -Name "ClaimsXray" -Identifier "urn:microsoft:adfs:claimsxray" -IssuanceAuthorizationRules $authzRules -IssuanceTransformRules $issuanceRules -WSFedEndpoint $redirectUrl -SamlEndpoint $samlEndpoint

2. Check IDP: use a browser to navigate to https://sts.kimconnect.com/adfs/ls/idpinitiatedsignon.aspx

3. Updating Self Signed Certs:

# Check Self Signed Certificates of ADFS. If AutoCertificateRollover = True, do nothing as certs will auto roll.
PS C:\Windows\system32> get-adfsproperties
AcceptableIdentifiers : {}
AddProxyAuthorizationRules : exists([Type ==
"http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value
== "S-1-5-32-544", Issuer =~ "^AD AUTHORITY$"]) => issue(Type =
"http://schemas.microsoft.com/authorization/claims/permit", Value =
"true");
c:[Type ==
"http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid",
Issuer =~ "^AD AUTHORITY$" ]
=> issue(store="_ProxyCredentialStore
",types=("http://schemas.micr
osoft.com/authorization/claims/permit"),query="isProxyTrustManagerSid({0})
", param=c.Value );
c:[Type ==
"http://schemas.microsoft.com/ws/2008/06/identity/claims/proxytrustid",
Issuer =~ "^SELF AUTHORITY$" ]
=> issue(store="_ProxyCredentialStore
",types=("http://schemas.micr
osoft.com/authorization/claims/permit"),query="isProxyTrustProvisioned({0}
)", param=c.Value );
ArtifactDbConnection : Data Source=tor-sql-node02.corp.kimconnect.com;Initial
Catalog=AdfsArtifactStore;Integrated Security=True;Min Pool Size=20
AuthenticationContextOrder : {urn:oasis:names:tc:SAML:2.0:ac:classes:Password,
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport,
urn:oasis:names:tc:SAML:2.0:ac:classes:TLSClient,
urn:oasis:names:tc:SAML:2.0:ac:classes:X509...}
AutoCertificateRollover : True
CertificateCriticalThreshold : 2
CertificateDuration : 365
CertificateGenerationThreshold : 20
CertificatePromotionThreshold : 5
CertificateRolloverInterval : 720
CertificateSharingContainer : CN=af389bbd-b0c0-405c-b965-bca5e7aa02e5,CN=ADFS,CN=Microsoft,CN=Program
Data,DC=corp,DC=kimconnect,DC=com
CertificateThresholdMultiplier : 1440
ClientCertRevocationCheck : None
ContactPerson : Microsoft.IdentityServer.Management.Resources.ContactPerson
DisplayName : kimconnect
IntranetUseLocalClaimsProvider : False
ExtendedProtectionTokenCheck : Allow
FederationPassiveAddress : /adfs/ls/
HostName : sts.kimconnect.com
HttpPort : 80
HttpsPort : 443
TlsClientPort : 49443
Identifier : http://sts.kimconnect.com/adfs/services/trust
InstalledLanguage : en-US
LogLevel : {Errors, Information, Verbose, Warnings}
MonitoringInterval : 1440
NetTcpPort : 1501
NtlmOnlySupportedClientAtProxy : True
OrganizationInfo :
PreventTokenReplays : True
ProxyTrustTokenLifetime : 21600
ReplayCacheExpirationInterval : 60
SignedSamlRequestsRequired : False
SamlMessageDeliveryWindow : 5
SignSamlAuthnRequests : False
SsoLifetime : 480
PersistentSsoLifetimeMins : 10080
KmsiLifetimeMins : 1440
PersistentSsoEnabled : True
PersistentSsoCutoffTime : 1/1/0001 12:00:00 AM
KmsiEnabled : False
LoopDetectionEnabled : True
LoopDetectionTimeIntervalInSeconds : 20
LoopDetectionMaximumTokensIssuedInInterval : 5
PasswordValidationDelayInMinutes : 60
SendClientRequestIdAsQueryStringParameter : False
WIASupportedUserAgents : {MSAuthHost/1.0/In-Domain, MSIE 6.0, MSIE 7.0, MSIE 8.0...}
ExtranetLockoutThreshold : 2147483647
ExtranetLockoutEnabled : False
ExtranetObservationWindow : 00:30:00
GlobalRelyingPartyClaimsIssuancePolicy : c:[Type == "http://schemas.microsoft.com/2012/01/devicecontext/claims/isre
gistereduser"] => issue(claim = c);c:[Type ==
"http://schemas.microsoft.com/2012/01/devicecontext/claims/identifier"]
=> issue(claim = c);
PromptLoginFederation : FallbackToProtocolSpecificParameters
PromptLoginFallbackAuthenticationType : urn:oasis:names:tc:SAML:1.0:am:password

4. Updating Public Certs:
– Add new public Cert into Local Machine
– Check Public Certificates of Local Machine

PS C:\Windows\system32> dir cert:LocalMachine\My
PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\My
Thumbprint Subject
---------- -------
1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ CN=DigiCert SHA2 High Assurance Server CA, OU=www.digicert.com, O=DigiCert...
1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ CN=ADFS01, CN=2a362dd8-a989-4573-b06c-b9515bfe5448, OU=Microsoft ADFS ...
1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ CN=ADFS01, CN=2a362dd8-a989-4573-b06c-b9515bfe5448, OU=Microsoft ADFS ...
1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ CN=*.kimconnect.com, O="Kim Connect, Inc.", L=Torrance, S=CA, C=US
1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ CN=ADFS Encryption - sts.kimconnect.com
1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ CN=DigiCert High Assurance EV Root CA, OU=www.digicert.com, O=DigiCert Inc...
1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ CN=ADFS Signing - sts.kimconnect.com
1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ CN=ADFS01, CN=2a362dd8-a989-4573-b06c-b9515bfe5448, OU=Microsoft ADFS ...
# Check Certificate association on ADFS

PS C:\Windows\system32> get-adfssslcertificate

HostName PortNumber CertificateHash
-------- ---------- ---------------
localhost 443 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ
sts.kimconnect.com 443 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ
sts.kimconnect.com 49443 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ
localhost 444 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ
sts.kimconnect.com 444 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ
# Update Pulblic Certificates in ADFS
set-adfssslcertificate -thumbprint <your newcert thumbprint>

5. Validate Relying Party Trusts
– CRM Claims Relying Party:
— Purpose: CRM main login authentication
— Pull federation metadata from https://auth.kimconnect.com/FederationMetadata/2007-06/FederationMetadata.xml
— Claim Rules: Pass through UPN, Pass Through Primary SID, Transform Windows Account Name
– CRM IFD Relying Party:
— Purpose: CRM sub-modules authentication
— Pull federation metadata from https://crm.kimconnect.com/FederationMetadata/2007-06/FederationMetadata.xml
— Claim Rules: Pass through UPN, Pass Through Primary SID, Transform Windows Account Name

How to Change a Disk Signature using Diskpart

Application: to potentially resolve issues with duplicate disk signature in a Windows Virtual Machine

Symptom:

Log Name:      System
Source: partmgr
Date: 8/29/2019 9:01:23 PM
Event ID: 58
Task Category: None
Level: Warning
Keywords: Classic
User: N/A
Computer: virtualmachine007
Description:
The disk signature of disk 4 is equal to the disk signature of disk 0.
Event Xml:
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<System>
<Provider Name="partmgr" />
<EventID Qualifiers="32772">58</EventID>
<Level>3</Level>
<Task>0</Task>
<Keywords>0x80000000000000</Keywords>
<TimeCreated SystemTime="2019-08-30T04:01:23.312782700Z" />
<EventRecordID>1847939</EventRecordID>
<Channel>System</Channel>
<Computer>virtualmachine007</Computer>
<Security />
</System>
<EventData>
<Data>
</Data>
<Data>4</Data>
<Data>0</Data>
<Binary>0000000003003000000000003A000480000000000000000000000000000000000000000000000000</Binary>
</EventData>
</Event>

1. Boot from the Windows install disc
2. Click on Repair your computer after selecting proper language, time and keyboard input.
3. Choose Command Prompt when the System Recovery Options box appears
4. Change into C:
5. Run diskpart

PS C:\> diskpart

Microsoft DiskPart version 6.3.9600

Copyright (C) 1999-2013 Microsoft Corporation.
On computer: TOR-FS01

DISKPART> list disk

Disk ### Status Size Free Dyn Gpt
-------- ------------- ------- ------- --- ---
Disk 0 Online 127 GB 0 B *
Disk 1 Reserved 10 TB 0 B *
Disk 2 Reserved 5120 MB 0 B *
Disk 3 Reserved 2048 GB 0 B *

DISKPART> select disk 0

Disk 0 is now the selected disk.

DISKPART> OFFLINE DISK
Diskpart successfully offlined the selected disk

DISKPART> uniqueid disk

Disk ID: {EA2ECF55-2B5C-41F6-A29D-1A1BC527FCF1}

DISKPART> UNIQUEID DISK ID=1234ABCD

DISKPART> ONLINE DISK
Diskpart successfully onlined the selected disk

DISKPART> LIST DISK

Disk ### Status Size Free Dyn Gpt
-------- ------------- ------- ------- --- ---
Disk 0 Online 127 GB 0 B *
Disk 1 Reserved 10 TB 0 B *
Disk 2 Reserved 5120 MB 0 B *
Disk 3 Reserved 2048 GB 0 B *

Some Useful Windows Commands to Troubleshoot Networking on Windoze

# Check this computer's trust relationship to its domain controllers
$domainName="intranet.kimconnect.com"
PS C:\Windows\system32> nltest /SC_QUERY:$domainName
Flags: 0
Trusted DC Name
Trusted DC Connection Status Status = 1311 0x51f ERROR_NO_LOGON_SERVERS
The command completed successfully

# Reset computer account password
$domainController="DC01.intranet.kimconnect.com"
$domainAdmin="domainAdmin"
netdom.exe resetpwd /s:$domainController /ud:$domainAdmin /pd:*

Output:
PS C:\Windows\system32> netdom.exe resetpwd /s:$domainController /ud:$domainAdmin /pd:*
Type the password associated with the domain user:
The machine account password for the local machine could not be reset.
The network path was not found.
The command failed to complete successfully.

# Check network interfaces
PS C:\Windows\system32> get-netadapter *
Name InterfaceDescription ifIndex Status MacAddress LinkSpeed
---- -------------------- ------- ------ ---------- ---------
DEV Cisco VIC Ethernet Interface #4 15 Up 00-25-B5 10 Gbps
iSCSI-A Cisco VIC Ethernet Interface #3 14 Up 00-25-B5 10 Gbps
CLUSTER Cisco VIC Ethernet Interface #2 8 Up 00-25-B5 10 Gbps
iSCSI-B Cisco VIC Ethernet Interface 2 Up 00-25-B5 10 Gbps

# Restart a NIC
PS C:\Windows\system32> get-netadapter -Name "PROD" | Restart-NetAdapter

# Overload a NIC with additional IP address(es)
$availableIP="x.x.x.x"
$cidrMask=24
$adapterName="DEV"
New-NetIPAddress –IPAddress $availableIP –PrefixLength $cidrMask –InterfaceAlias $adapterName –SkipAsSource $True

PS C:\Windows\system32> New-NetIPAddress –IPAddress $availableIP –PrefixLength $cidrMask –InterfaceAlias $adapterName –SkipAsSource $True
IPAddress : x.x.x.x
InterfaceIndex : 15
InterfaceAlias : PROD
AddressFamily : IPv4
Type : Unicast
PrefixLength : 24
PrefixOrigin : Manual
SuffixOrigin : Manual
AddressState : Tentative
ValidLifetime : Infinite ([TimeSpan]::MaxValue)
PreferredLifetime : Infinite ([TimeSpan]::MaxValue)
SkipAsSource : True
PolicyStore : ActiveStore

# Attempt to ping DNS1 from the new IP - failure means no ICMP outbound allowed
PS C:\Windows\system32> ping 1.1.1.1 -i x.x.x.x
Pinging 1.1.1.1 with 32 bytes of data:
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Ping statistics for 1.1.1.1:
Packets: Sent = 4, Received = 0, Lost = 4 (100% loss)

# Set static route. Warning: don't do this unless you really know routing and switching!
$subnet="1.1.1.0"
$cidrMask=24
$ifIndex=15
$nextHop="1.1.1.1"
New-NetRoute -DestinationPrefix "$subnet`/$cidrMask" -InterfaceIndex $ifIndex -NextHop $nextHop
Get-NetRoute

PowerShell: Microsoft Failover Cluster Discovery Version 0.10

<#  .Sypnosis: Microsoft_Clusters_Discovery_v.0.10.ps1
What it does:
- Installs Microsoft Failover Cluster PowerShell module on the local machine (Jump Box).
This can work behind a proxy if those variables are specified.
Be advised that RSAT-Clustering-PowerShell currently has known conflicts with VMWare.PowerCLI.
Thus, this must be ran on machines that does not already have VMWare.PowerCLI installed.
- Collects all FailOver Clusters in the current domain
- Lists all nodes of a selected cluster
- Checks whether Remote PowerShell is enabled on a selected node
- Connects to a user-selected node via WinRM and queries for clustering information: resource names and current owners

Requirements:
- Script must be ran using a Domain Admin account
- It can only query remote machines with WinRM being enabled
- Requires a manual trigger of PowerShell Execution Policy: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Confirm:$false -Force

Future development:
- Runs as a Domain Admin
- Uses PSExec to enable WinRM on remote machines prior to executing Remote Powershell invoke-command
- Describes the clustering types and trigger selected functions to match the type of resource (SQL, Hyper-V, FileServer, etc.)
#>

$proxy="http://proxy:8080"
$exclusionList="localhost;*.sdcs.local"

<# Future development: Domain Admin credential prompts
# Credential for PS-Remoting
$usernameInput=Read-Host -Prompt "Input the username";
$username="$($env:USERDNSDOMAIN)\$usernameInput";
$password = Read-Host -Prompt "Input the password for account $username" -AsSecureString
#$password=convertto-securestring "PASSWORD" -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username,$password
#>

################################## Excuting Program as an Administrator ####################################
# Get the ID and security principal of the current user account
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$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
}

Write-Host -NoNewLine "Running as Administrator..."
################################## Excuting Program as an Administrator ####################################

function checkProxy{
try{
$connectionTest=iwr download.microsoft.com -UseBasicParsing
#$connectionSucceeds=Test-NetConnection -Computername download.microsoft.com -Port 443 -InformationLevel Quiet
if ($connectionTest){
return $True;
}
}
catch{
return $False
}
}

function fixProxy{
# Check if proxy is enabled on the system and fix it
$proxyKey=(Get-ItemProperty -Path "Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings")
if ($proxyKey.ProxyEnable){
# Set http proxy for browsers
Set-Itemproperty -path "Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" -Name 'ProxyServer' -value $proxy

# Set winhttp proxy for PowerShell
netsh winhttp set proxy $proxy $exclusionList

[system.net.webrequest]::defaultwebproxy = New-Object system.net.webproxy($proxy)
[system.net.webrequest]::defaultwebproxy.credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
[system.net.webrequest]::defaultwebproxy.BypassProxyOnLocal = $true
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
}

if (checkProxy){
"Proxy is now good to go..."
}
else{
"Proxy problems..."
break;
}
}

# Set PowerShell Gallery as Trusted to bypass prompts
Function setPSGalleryTrust{
$trustPSGallery=(Get-psrepository -Name 'PSGallery').InstallationPolicy
If($trustPSGallery -ne 'Trusted'){
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
}
}

# Adding Prerequisite Active Directory Module
Function installADModule{
if (!(get-module -name "ActiveDirectory") ){
Add-WindowsFeature RSAT-AD-PowerShell | out-null;
import-module -name "ActiveDirectory" -DisableNameChecking | out-null;
}
$domain=get-addomain
}

# Adding Prerequisite Microsoft Cluster
Function installFailoverClustersModule{
if (!(get-module -Name "FailoverClusters") ){
#Install-WindowsFeature Failover-Clustering | out-null;
Install-WindowsFeature RSAT-Clustering-MGMT | out-null;
Install-WindowsFeature RSAT-Clustering-PowerShell | out-null;
Import-Module FailoverClusters | out-null;
}
}

Function installPrerequisites{
setPSGalleryTrust;
#installADModule;
installFailoverClustersModule;
}

function listClusters{
"`n`nListing all Clusters in $($env:USERDNSDOMAIN)...`n"
$GLOBAL:clusters=get-cluster -domain $env:USERDNSDOMAIN
}

function listNodes{
"`n`nAttempting to collect a list of all Nodes in this Cluster...`n"
try{
$global:nodes=(Get-ClusterNode -Cluster "$pickedItem.$env:USERDNSDOMAIN").Name
$global:pickedCluster=$pickedItem
}
catch{
$_.Exception.Message;
}
#$global:headLine="Available nodes in $pickedItem`:"
}

function showMenu{
Param($callFunction)
invoke-command (Get-Item "function:$callFunction").ScriptBlock

switch($callFunction){
"listClusters"{$count=$clusters.count; $global:list=$clusters;}
"listNodes" {$count=$nodes.count; $global:list=$nodes;}
}

$show="`n--------------------------------------------------------`nThere are $count items on this list:`n--------------------------------------------------------`n"
for ($row=0;$row -le $count-1;$row++){
$server=$list[$row]
$show += "$row" + ": " + "$server" + "`n"
}
return $show
}

function pickItem{
do {
try {
$flag = $true
[int]$pick=Read-Host -Prompt "`n---------------------------------------`nPlease pick an item from the above list`n---------------------------------------"
} # end try
catch {$flag = $false}
} # end do
until ($pick -lt $list.count -and $flag)

$global:pickIndex=$pick
$global:pickedItem=$list[$pick]
$clusters=""
$nodes=""
"$pick`: $pickedItem is picked"
}

# return true or false as determinant on whether program proceeds
function checkNodesForWinRM{
$nodes | foreach {
$winRM=Test-NetConnection -ComputerName "$_`.$($env:USERDNSDOMAIN)" -CommonTCPPort WINRM -InformationLevel Quiet
if ($winRM){"$_ is reachable via WinRM.";}
}
}

function queryFailoverClusters{

# Pick Cluster
do {
try{
$flag1=$true
showMenu -callFunction listClusters
pickItem
}
catch{$flag1=$false}
} until ($flag1)

# Pick Node
do {
try{
$flag2=$true
showMenu -callFunction listNodes
if (!($nodes)){queryFailoverClusters;}
checkNodesForWinRM;
pickItem
}
catch{$flag2=$false}
} until($flag2)
}

$failoverClusterModuleAvailable=get-cluster
if (!($failoverClusterModuleAvailable)){
if(!(checkProxy)){"Internet issues detected. Attempting a fix now..."; fixProxy;}
installPrerequisites;
}

function failoverClusterDiscovery{
queryFailoverClusters;

$pickedItemWinRM=Test-NetConnection -ComputerName "$pickedItem`.$($env:USERDNSDOMAIN)" -CommonTCPPort WINRM -InformationLevel Quiet
if($pickedItemWinRM){
"Obtaining Cluster Resource from node $pickedItem..."

# Triggering invoke-command as multi-threaded jobs so that the program will wait until these jobs are done before moving to the Pause command
$jobs = Invoke-Command -ComputerName $pickedItem -ScriptBlock {Get-ClusterResource | format-list Name,OwnerNode;} -AsJob
While ($Jobs.State -Contains "Running"){
$Jobs | Wait-Job -Any;
if ($jobs.State -contains "Completed") {
Receive-Job -Job $jobs;
Remove-Job -Job $jobs;
}
}

#Invoke-Command -ComputerName $pickedItem -ScriptBlock{Get-ClusterResource;}
}else{
"$pickedItem is not reachable via Remote PowerShell to query Cluster Resources.";
pause;
}
}

do {
failoverClusterDiscovery;
[string]$response=Read-Host -Prompt "`nPress any key to repeat process. Type 'exit' or use Ctrl+C to terminate program"
} until ($response -like "exit")