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

<# Daily-VSS-Snapshots.ps1

Functions:
1. Dynamically detect all volumes on local machine
2. Dynamically take snapshots of each volume and mount them onto C:\Snapshots\$hostname\$volumeLabel\$snapshotTimeStamp
3. Copy symlinks of those local VSS client access links to a network share in this format (\\Snapshots\FileServerClusters\$clusterName\$hostname\$volumeLabel\$timeStamp)
4. 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="[BC]\:"
#$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=7;

# 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 -ge $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 $remoteSnapshotPath -olderThanXDays $daysOlderThan
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')",
[string]$remoteSnapshot="$remoteSnapshotPath\$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;
New-Item -ItemType Directory -Force -Path "$remoteSnapshotPath\$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 /d $remoteSnapshot $localShapshot;
#cmd /c mklink /d $snapshotPath $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: $remoteSnapshot"

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

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

New-Item -ItemType Directory -Force -Path $remoteSnapshotPath | Out-Null;
New-Item -ItemType Directory -Force -Path $localSnapshotPath | Out-Null;

enableR2RSymbolicLinks;
if ((get-item $localSnapshotPath) -and (get-item $remoteSnapshotPath) -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')";
$snapshotRemoteCopy="$remoteSnapshotPath\$hostname\$volumeLabel\$(Get-Date -Format 'yyyy-MM-dd_hh.mm.ss')"

if(checkDiskFree -volume $_){
createNewSnapShot -targetVolume $_ -label $volumeLabel -localShapshot $snapshotLink -remoteSnapshot $snapshotRemoteCopy;
}else{"Volume $_ does NOT have sufficient disk space available for taking snapshots."}
}
removeSnapshots -daysOlderThan $retentionPeriod
}else{"Program aborted due to missing items."}
}

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")

Windows File System Symlink Settings

Issue: Shortcuts or symbolic links are inaccessible at the destination, after being synchronized (using robocopy, xcopy, emcopy, etc.)

---------------------------
Open Folder
---------------------------
\\FILESHERVER01\Users\tester\Music 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.
---------------------------
OK
---------------------------

Check local file system settings on Symlink behavior:

PS C:\Users\admindude> fsutil behavior query SymlinkEvaluation
Local to local symbolic links are enabled
Local to remote symbolic links are enabled.
Remote to local symbolic links are disabled.
Remote to Remote symbolic links are disabled.

Enable Remote to remote symlink following option

fsutil behavior set SymlinkEvaluation R2R:1

PowerShell: Use EMCOPY to Mirror a Directory

# Purpose: this PowerShell snippet is to demonstrate the use of Emcopy

$source="C:\Users\tester\Desktop\Clients"
$destination="C:\Users\tester\Desktop\Test"
#$switches="/o /secforce /s /de /sd /c /r:0 /th 32 /cm md5 /purge /sdd"
$switches="/o /secforce /de /sd /c /r:0 /th 32 /s /purge /sdd"
<# Switch explanations
/s copies sub directories
/purge removes files and directories from the destination that do not exist in the source.
/sdd forces the target directories dates to be synchronized with the source directory.
/de Compares both file size and last modification time when deciding to update a file, updates it if either have been changed.
/cm md5 - checks the file content after copying using and md5 comparison of the source and destination.
/o copies the files owner, without this the account used for the copy will be the owner
/secforce overwrites the destination security settings with the source security settings (copies security settings)
/sd preserves security, the file isn't copied if an error occurs during security settings.
/th 32 - Uses 32 threads, default is 64
/r:0 retries zero times
/w:0 is the wait time in seconds between retries
/c will allow the process to continue after the retries
/log:filename option allows to redirect the console messages to a new file.
/log+:filename option appends the new messages to an existing file.
#>

$dateStamp = Get-Date -Format "yyyy-MM-dd-hhmmss"
$scriptName=$MyInvocation.MyCommand.Path
$scriptPath=Split-Path -Path $scriptName
$logPath="$scriptPath\emcopy_logs"
$logFile="$logPath\robocopy-log-$dateStamp.txt"
$log=" /LOG+:$logFile"


# 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
{
"Relaunching as an 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
}

# Run your code that needs to be elevated here
Write-Host -NoNewLine "Running as Administrator..."
#$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

function installEmcopy{
$emcopyIsInstalled=(Get-Command emcopy.exe -ErrorAction SilentlyContinue) # Deterministic check on whether emcopy is already available on this system
if (!($emcopyIsInstalled)){
$tempDir="C:\Temp";
$extractionDir="C:\Windows"
$source = "https://kimconnect.com/wp-content/uploads/2019/08/emcopy.zip";
$destinationFile = "$tempDir\emcopy.zip";
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
New-Item -ItemType Directory -Force -Path $tempDir
New-Item -ItemType Directory -Force -Path $extractionDir
$webclient = New-Object System.Net.WebClient;
$WebClient.DownloadFile($source,$destinationFile);
Expand-Archive -LiteralPath $destinationFile -DestinationPath $extractionDir
}else{
"EMCOPY is currently available in this system.`n";
}
}

function startEmcopy{
New-Item -ItemType Directory -Force -Path $logPath;
"Confirm this statement:`nemcopy64.exe $source $destination $switches $log";
pause;
invoke-expression "emcopy64.exe $source $destination $switches $log"
"emcopy process is finished. Log is generated here: $log"
pause;
}

installEmcopy;
startEmcopy;

A typical error:

TH000 : 20:32:21 : WARNING : Unable to retrieve the PDC of the domain intranet nearby \\FILESHERVER007, error : 1210
TH000 : 20:32:21 : WARNING : The default primary group is set to "domain users"

Resolution:

  • Identify the user account used for running EMCopy (admin account you are using)
  • Add that account to the local administrator group and local backup group on the Windows file server
  • Allow that account to “run as root” on the Isilon share

PowerShell: File Copying Operation

Version 0.16
<#
.Description File_Copy_Script Version: 0.16

Purpose: this PowerShell Script is to efficiently mirror large batches of files using Emcopy in conjunction with Volume Shadow Services

Current Features:
1. Check for any errors on the Sources or Destinations and generate a report of any extra spaces in UNC
2. Create a snapshot of the source volume using Shadow Copy to capture any locked files
3. Execute robocopy to mirror the source to its corresponding destination with a time stamp variance allowance of 2 seconds for speed and resiliency
4. Sample the copied files to 'spot check' any time stamp variances
5. Execute in the context of an Administrator

Features planned for development:
6. Enable Volume Shadow Copy (VSS) at Source machines if it has been disabled, and reverse the action when done with copying
7. Trigger Remote Powershell to launch execution from a middle server (a "jump box" that is not a source nor destination)
if the provided Source is detected as a Universal Naming Convention (UNC) path instead of a local file system (LFS) path

Limitations:
1. This iteration requires that script is triggered from a local Windows machine with Internet access (no proxies) to download dependencies
2. Source must be LFS and Destination could either be LFS or UNC

Troubleshooting:

# Create snapshot
$targetVolume="M:\"
$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 + "\";
C:
cmd /c mklink /d "C:\shadowcopy" $thisShadowPath

# Remove snapshot
$thisShadow.Delete()
$thisSnapshot.Dispose()
(Get-Item "C:\shadowcopy").Delete()

# Delete all shadows
vssadmin delete shadows /all /Quiet
#>

# Specify Sources (LFS) and Destinations (UNC)
# Using brackets notation to create a two dimensional array, overcoming PowerShell's limitations
$arr=@{}
$arr["from"] = @{}; $arr["to"] = @{}
$arr["from"] = @("C:\Users\Kim\Desktop\Tech_Documentation"); $arr["to"]=@("C:\Users\Kim\Desktop\Test")
$arr["from"] += "C:\Users\Kim\Desktop\Test"; $arr["to"]+="C:\Users\Kim\Desktop\Test1"

# Init global variables
$GLOBAL:totalTime=0;
$GLOBAL:totalStorage=0;
$GLOBAL:sampleSize=1000;
$GLOBAL:shadowMount="C:\shadowcopy"
$GLOBAL:toCreateShadow=$True
$GLOBAL:previousDriveLetter="none"
$GLOBAL:thisDriveLetter="none"
$GLOBAL:nextDriveLetter="none"
$clusterSizeOfNetworkShare=16384

# Initialize log files
$dateStamp = Get-Date -Format "yyyy-MM-dd-hhmmss"
$scriptName=$MyInvocation.MyCommand.Path
$scriptPath=Split-Path -Path $scriptName
$logPath="$scriptPath\filecopy_logs"
$logFile="$logPath\filecopy-log-$dateStamp.txt"
$log=" /LOG+:$logFile"
$lockedFilesReport="$logPath\_locked-files-log-$dateStamp.txt"
$pathErrorsLog="$logPath`\_path-errors-log-$dateStamp.txt"

# Emcopy switches
$switches="/o /secforce /de /sd /c /r:0 /w:0 /th 128 /s /purge /sdd /stream"
<# Switch explanations
/s copies sub directories
/purge removes files and directories from the destination that do not exist in the source.
/sdd forces the target directories dates to be synchronized with the source directory.
/de Compares both file size and last modification time when deciding to update a file, updates it if either have been changed.
/cm md5 - checks the file content after copying using and md5 comparison of the source and destination.
/o copies the files owner, without this the account used for the copy will be the owner
/secforce overwrites the destination security settings with the source security settings (no merging, actual update of security settings at the destination)
/sd preserves security, the file isn't copied if an error occurs during security settings.
/th 128 - Uses 128 threads, default is 64
/r:0 retries zero times
/w:0 is the wait time in seconds between retries
/c will allow the process to continue after the retries
/log:filename option allows to redirect the console messages to a new file.
/log+:filename option appends the new messages to an existing file.
/stream option enables the copie of files and directories datastreams. Without that option only the main datastream of files are copied.
#>

################################## 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 ####################################

################################## Commence Programming Sequence ####################################
New-Item -ItemType Directory -Force -Path $logpath;
"Executing copying tasks..."

function createShadow(){
[cmdletbinding()]
param(
[string]$targetVolume="C:\"
)
if (!($targetVolume -like "*\")){$targetVolume+="\"}

if ($toCreateShadow){
$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 + "\"
C:
cmd /c mklink /d $shadowMount $thisShadowPath
"Shadow of $targetVolume has been made and it's accessible at this local file system (LFS): $shadowMount."
#copyLockedFiles; # this function is to be developed: retrieve lock files list, copy each item on list
#deleteShadow $thisShadow $thisShadowMount

# Export variables
$GLOBAL:shadow=$thisShadow;
$GLOBAL:snapshot=$thisSnapshot;

}else{
"Not creating a duplicate shadow in this iteration.";
}
}

function deleteShadow(){
if ($nextDriveLetter -eq $thisDriveLetter){
"Not removing snapshot because next one is on the same drive letter.";
}else{
# Remove symlink
(Get-Item $shadowMount).Delete()

# delete single instance of volume snapshots
$shadow.Delete()
$snapshot.Dispose()

# Delete all instances of volume snapshots
#Get-WmiObject Win32_ShadowCopy | % {$_.delete()}

"Shadow link $shadowMount has been removed."
}
}

function logPathError($pathError){
Add-Content $pathErrorsLog "$pathError";
}

function validateDirectory($dirToValidate){
if(Test-Path -Path $dirToValidate){return $True}
else{return $False;}
}

function createDirectory($dir){
# Create folder if it doesn't exist
if(!(validateDirectory $dir)){
New-Item -path $dir -type directory
}
}

function validateSourceAndDestination($thisblock){
$spacesCount=($thisblock.Split(' ')).Count-1
if ($spacesCount -eq 1){
$GLOBAL:source,$GLOBAL:destination=$thisblock.split(' ');
$sourceTest=validateDirectory $source
$destinationTest=validateDirectory $destination
if ($sourceTest -and $destinationTest){
return $True;
}
else {
if (!($sourceTest)){
logPathError "Source: $source";
return $False;
}
if (!($destinationTest)){
$createDestinationPath=(Read-Host -Prompt "Destination: $destination does not exist.`nType 'y' or 'yes' to create.");
if ($createDestinationPath -like 'yes' -or $createDestinationPath -like 'y'){
createDirectory $destination;
return $True;
}
else{
logPathError "Destination: $destination";
return $False
}
}
return $False;
}
}
else {
logPathError $thisblock;
return $False;
}
}

function translateSource{
param(
[string]$uncPath
)
$uri = new-object System.Uri($uncPath)
$thisLocalPath=$uri.LocalPath
#$thisHost=$uri.Host
$GLOBAL:sourceVolume="$((Get-Item $thisLocalPath).PSDrive.Name)`:"
$GLOBAL:translatedSource=$thisLocalPath -replace "$sourceVolume", $shadowMount
}

function sampleTimeStamp{
# 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."
if (!($r2rEnabled)){fsutil behavior set SymlinkEvaluation R2R:1;}

#$sourceFiles=Get-ChildItem $source -recurse | ? { !($_.PsIsContainer -and $_.FullName -notmatch 'archive' -and $_.FullName.Length -lt 260) } | get-random -count $sampleSize | % {$_.FullName}
$sourceFiles=Get-ChildItem $source -recurse | get-random -count $sampleSize | % {$_.FullName}
$commonDenominatingPaths=$sourceFiles | %{$_.replace($source,'')}
$destinationFiles=$commonDenominatingPaths | %{"$destination"+"$_";}
$badStamps=0;

if ($sourceFiles[0].Length -gt 1){
"Checking a sample of $sampleSize for any time stamp inaccuracy..."
for ($i=0;$i -lt $sourceFiles.length;$i++){
$sourceFile=$sourceFiles[$i];
$destinationFile=$destinationFiles[$i];
if ($destinationFile.Length -lt 260){
$sourceTimeStamp=(gi $sourceFile).LastWriteTime;
$destinationTimeStamp=(gi $destinationFile).LastWriteTime;
if ($sourceTimeStamp -eq $destinationTimeStamp){
#$output+="`r`n$destinationFile is GOOD";
} else {
$output+="`r`n$destinationFile timestamp of $destinationTimeStamp DOES NOT MATCH its source $sourceFile timestamp of $sourceTimeStamp";
$badStamps++;
}
} else {
$output+="`r`nException: $destinationFile length is $($destinationFile.Length)";
}
}
$output="`r`n`r`n------------$((($sourceFiles.Length-$badStamps)/$sourceFiles.Length).tostring('P')) of the files in a sample of $($sourceFiles.Length) are having accurate time stamps--------------`n"+$output;
}else{$output="`r`n`r`n------------Insufficient number of files to process.------------`r`n";}

$output;
Add-Content $logFile $output;
}

function checkDiskFree{
<#
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="localhost"
$thisVolume=$sourceVolume

# 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 expandZipfile($file, $destination){
$shell = new-object -com shell.application
$zip = $shell.NameSpace($file)

foreach($item in $zip.items()){
$shell.Namespace($destination).copyhere($item)
}
}

function installEmcopy{
$emcopyIsInstalled=(Get-Command emcopy.exe -ErrorAction SilentlyContinue) # Deterministic check on whether emcopy is already available on this system
if (!($emcopyIsInstalled)){
$tempDir="C:\Temp";
$extractionDir="C:\Windows"
$source = "https://kimconnect.com/wp-content/uploads/2019/08/emcopy.zip";
$destinationFile = "$tempDir\emcopy.zip";
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
New-Item -ItemType Directory -Force -Path $tempDir
New-Item -ItemType Directory -Force -Path $extractionDir
$webclient = New-Object System.Net.WebClient;
$WebClient.DownloadFile($source,$destinationFile);
expandZipfile $destinationFile -Destination $extractionDir
}else{
"EMCOPY is currently available in this system.`n";
}
}

function setACL{
get-acl -Path $source | Set-Acl -Path $destination # This command requires PowerShell 5
}

function startEmcopy{
param(
[string]$sourceAndDestination
)
"Emcopy has started..."
try{
invoke-expression "emcopy.exe $sourceAndDestination $switches $log";
}
catch{
# Record any errors into log and continue to next item
continue;
}
"`r`nEmcopy process has finished. Log is now generated at: $log";
}

Function startCopy($from,$to){
$GLOBAL:source=$from;
$GLOBAL:destination=$to;
$GLOBAL:block="$source $destination"
$time=0;
$stopWatch= [System.Diagnostics.Stopwatch]::StartNew()

if (validateSourceAndDestination $block){
translateSource $source;
#"Shadow Copy source: $tranlatedSource`nDestination: $destination";
$translatedBlock="$translatedSource $destination";
if (checkDiskFree){
createShadow $sourceVolume;
installEmcopy;
Try{
startEmcopy $translatedBlock;
}
catch{
continue;
}
deleteShadow;
sampleTimeStamp;
"Log for this activity has been generated at $logFile"
}else{"Not enough disk space to create a VSS snapshot.`r`Skipping this iteration..."}
}

# Getting storage estimate
#$storage=(Get-ChildItem $source -Recurse | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1GB;
#$storageDestination=(Get-ChildItem $destination -Recurse | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1GB;
$storage=(getSizeOnDisk -path $source) / 1GB;
$storageDestination=(getSizeOnDisk -path $destination) / 1GB;
$percentDifference=[math]::Round((($storageDestination-$storage)/$storage)*100),2)
$GLOBAL:totalStorage+=$storage;
$totalStorage;

# Stop the timer
$time=$stopWatch.Elapsed.TotalSeconds;
#$elapsedDisplay=([timespan]::fromseconds($elapsedSeconds)).ToString().Split('.')[0]
$GLOBAL:totalTime+=$time;
$totalTime;

# Add total time display to log
$timeDisplay = ([timespan]::fromseconds($time)).ToString()
Add-Content $logFile "`r`n------------------------------Time Elapsed: $timeDisplay------------------------------";

# Record overall speed to log
$speed=($storage/$time)*3600
Add-Content $logFile "`r`n---------------------------Speed: $([math]::Round($speed,4)) GiB per hour---------------------------";

# Compare storage results
Add-Content $logFile "`r`n---------------------------$(if($percentDifference -ge 0){"$percentDifference`% loss";}else{"$([math]::abs($percentDifference))`% gain";})`: Source $([math]::Round($storage,4)) GiB vs Destination $([math]::Round($storageDestination,4)) GiB---------------------------";
}

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

$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 | where { !$_.PSisContainer }|% {
$size = [Win32.Disk]::GetSizeOnDisk($_.FullName)
#$AFZ=[MidpointRounding]::AwayFromZero
#$roundedSize=[math]::Round($size/$ClusterSize+0.5,$AFZ)*$ClusterSize
if($size -gt $clusterSize){$roundedSize=[math]::ceiling($size/$clusterSize)*$clusterSize}else{$roundedSize=$clusterSize;}
$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;
}

function proceed{
$initInfo="=============================================Job Started: $dateStamp=============================================`r`n";
$initInfo+="Powershell version detected: $($PSVersionTable.PSVersion.Major)`.$($PSVersionTable.PSVersion.Minor)`r`n"
$initInfo+="Processing the following operations:`r";
$initInfo+=for ($i=0;$i -lt $arr.from.count; $i++){"$($arr.from[$i]) => $($arr.to[$i])`r";}
$initInfo;
Add-Content $logFile $initInfo;

# Process the copying operations
for ($i=0;$i -lt $arr.from.count; $i++){
$processDisplay="=======================================Pass $($i+1)`: $($arr.from[$i]) => $($arr.to[$i])=======================================`r";
Add-Content $logFile $processDisplay;
$GLOBAL:thisDriveLetter=Split-Path -Path $arr.from[$i] -Qualifier
$GLOBAL:nextDriveLetter=try{Split-Path -Path $arr.from[$i+1] -Qualifier}catch{$false;}
"Previous drive $previousDriveLetter vs current $thisDriveLetter vs next $nextDriveLetter"
if($previousDriveLetter -ne $thisDriveLetter){$GLOBAL:toCreateShadow=$True}else{$GLOBAL:toCreateShadow=$False}
startCopy $arr.from[$i] $arr.to[$i];
$GLOBAL:previousDriveLetter=$thisDriveLetter;
# cmd /c pause | out-null;
}

# Record overall speed to log
$aggregateSpeed=($totalStorage/$totalTime)*3600
$summary="`r`n==============================Program has completed.==============================`r";
$summary+="`r`n==============================$([math]::Round($totalStorage,4)) GiB has been processed in $([math]::Round($totalTime/3600,4)) hours(s)==============================`r"
$summary+="`r`n==============================Jobs Completed with Total Aggregate Speed: $([math]::Round($aggregateSpeed,4)) GiB per hour=============================="
$summary;
Add-Content $logFile $summary;
}

function checkInputs{
"Checking inputs for bad paths..."
$GLOBAL:proceed=$True
for ($i=0;$i -lt $arr.from.count; $i++){
$from=$arr.from[$i]
$to=$arr.to[$i]
if(!(test-path $from)){$from;$GLOBAL:proceed=$False}
if(!(test-path $to)){$to;$GLOBAL:proceed=$False}
}
}

checkInputs;
$proceed
if($proceed){proceed;}else{"Paths validation has failed. Program will not proceed."}
################################## Main Programming Sequence ####################################
pause;
#cmd /c pause | out-null;
Version 0.15
<# .Description File_Copy_Script Version: 0.15

Purpose: this PowerShell Script is to efficiently mirror large batches of files using Emcopy in conjunction with Volume Shadow Services

Current Features:
1. Check for any errors on the Sources or Destinations and generate a report of any extra spaces in UNC
2. Create a snapshot of the source volume using Shadow Copy to capture any locked files
3. Execute emcopy to mirror the source to its corresponding destination with a time stamp variance allowance of 2 seconds for speed and resiliency
4. Sample the copied files to 'spot check' any time stamp variances
5. Execute in the context of an Administrator

Features planned for development:
6. Enable Volume Shadow Copy (VSS) at Source machines if it has been disabled, and reverse the action when done with copying
7. Trigger Remote Powershell to launch execution from a middle server (a "jump box" that is not a source nor destination)
if the provided Source is detected as a Universal Naming Convention (UNC) path instead of a local file system (LFS) path

Limitations:
1. This iteration requires that script is triggered from a local Windows machine with Internet access (no proxies)
2. Source must be LFS and Destination could either be LFS or UNC
#>

# Specify Source and Destination
$source="C:\Users\Test\Desktop\Clients" # Must be a LFS path
$destination="C:\Test\Kim\Desktop\Test"
$block="$source $destination"

# Emcopy switches
$switches="/o /secforce /de /sd /c /r:0 /w:0 /th 128 /s /purge /sdd /stream"
<# Switch explanations
/s copies sub directories
/purge removes files and directories from the destination that do not exist in the source.
/sdd forces the target directories dates to be synchronized with the source directory.
/de Compares both file size and last modification time when deciding to update a file, updates it if either have been changed.
/cm md5 - checks the file content after copying using and md5 comparison of the source and destination.
/o copies the files owner, without this the account used for the copy will be the owner
/secforce overwrites the destination security settings with the source security settings (no merging, actual update of security settings at the destination)
/sd preserves security, the file isn't copied if an error occurs during security settings.
/th 128 - Uses 128 threads, default is 64
/r:0 retries zero times
/w:0 is the wait time in seconds between retries
/c will allow the process to continue after the retries
/log:filename option allows to redirect the console messages to a new file.
/log+:filename option appends the new messages to an existing file.
/stream option enables the copie of files and directories datastreams. Without that option only the main datastream of files are copied.
#>

# Initialize log files
$dateStamp = Get-Date -Format "yyyy-MM-dd-hhmmss"
$scriptName=$MyInvocation.MyCommand.Path
$scriptPath=Split-Path -Path $scriptName
$logPath="$scriptPath\emcopy_logs"
$logFile="$logPath\emcopy-log-$dateStamp.txt"
$log=" /LOG+:$logFile"
$lockedFilesReport="$logPath\_locked-files-log-$dateStamp.txt"
$pathErrorsLog="$logPath`\_path-errors-log-$dateStamp.txt"

# Init other variables
$sampleSize=1000;
$GLOBAL:shadowMount="C:\shadowcopy"

################################## 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 = "White"
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 ####################################

################################## Commence Programming Sequence ####################################
New-Item -ItemType Directory -Force -Path $logpath;
"Executing copying tasks..."

function createShadow(){
[cmdletbinding()]
param(
[string]$targetVolume="C:\"
)
if (!($targetVolume -like "*\")){$targetVolume+="\"}
$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 + "\"
C:
cmd /c mklink /d $shadowMount $thisShadowPath
"Shadow of $targetVolume has been made and it's accessible at this local file system (LFS): $shadowMount."
#copyLockedFiles; # this function is to be developed: retrieve lock files list, copy each item on list
#deleteShadow $thisShadow $thisShadowMount

# Export variables
$GLOBAL:shadow=$thisShadow;
}

function deleteShadow(){
# Remove symlink
(Get-Item $shadowMount).Delete()

# delete single instance of volume snapshots
$shadow.Delete()

# Delete all instances of volume snapshots
#Get-WmiObject Win32_ShadowCopy | % {$_.delete()}

"Shadow link $shadowMount has been removed."
}

function logPathError($pathError){
Add-Content $pathErrorsLog "$pathError";
}

function validateDirectory($dirToValidate){
if(Test-Path -Path $dirToValidate){return $True}
else{return $False;}
}

function createDirectory($dir){
# Create folder if it doesn't exist
if(!(validateDirectory $dir)){
New-Item -path $dir -type directory
}
}

function validateSourceAndDestination($thisblock){
$spacesCount=($thisblock.Split(' ')).Count-1
if ($spacesCount -eq 1){
$GLOBAL:source,$GLOBAL:destination=$thisblock.split(' ');
$sourceTest=validateDirectory $source
$destinationTest=validateDirectory $destination
if ($sourceTest -and $destinationTest){
return $True;
}
else {
if (!($sourceTest)){
logPathError "Source: $source";
return $False;
}
if (!($destinationTest)){
$createDestinationPath=(Read-Host -Prompt "Destination: $destination does not exist.`nType 'y' or 'yes' to create.");
if ($createDestinationPath -like 'yes' -or $createDestinationPath -like 'y'){
createDirectory $destination;
return $True;
}
else{
logPathError "Destination: $destination";
return $False
}
}
return $False;
}
}
else {
logPathError $thisblock;
return $False;
}
}

function translateSource{
param(
[string]$uncPath
)
$uri = new-object System.Uri($uncPath)
$thisLocalPath=$uri.LocalPath
#$thisHost=$uri.Host
$GLOBAL:sourceVolume="$((Get-Item $thisLocalPath).PSDrive.Name)`:"
$GLOBAL:translatedSource=$thisLocalPath -replace "$sourceVolume", $shadowMount
}

function sampleTimeStamp{
# 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."
if (!($r2rEnabled)){fsutil behavior set SymlinkEvaluation R2R:1;}

#$sourceFiles=Get-ChildItem $source -recurse | ? { !($_.PsIsContainer -and $_.FullName -notmatch 'archive' -and $_.FullName.Length -lt 260) } | get-random -count $sampleSize | % {$_.FullName}
$sourceFiles=Get-ChildItem $source -recurse | get-random -count $sampleSize | % {$_.FullName}
$commonDenominatingPaths=$sourceFiles | %{$_.replace($source,'')}
$destinationFiles=$commonDenominatingPaths | %{"$destination"+"$_";}
$badStamps=0;

"Checking a sample of $sampleSize for any time stamp inaccuracy..."

for ($i=0;$i -lt $sourceFiles.length;$i++){
$sourceFile=$sourceFiles[$i];
$destinationFile=$destinationFiles[$i];
if ($destinationFile.Length -lt 260){
$sourceTimeStamp=(gi $sourceFile).LastWriteTime;
$destinationTimeStamp=(gi $destinationFile).LastWriteTime;
if ($sourceTimeStamp -eq $destinationTimeStamp){
#$output+="`r`n$destinationFile is GOOD";
} else {
$output+="`r`n$destinationFile timestamp of $destinationTimeStamp DOES NOT MATCH its source $sourceFile timestamp of $sourceTimeStamp";
$badStamps++;
}
} else {
$output+="`r`nException: $destinationFile length is $($destinationFile.Length)";
}
}
$output="`r`n`r`n------------$((($sourceFiles.Length-$badStamps)/$sourceFiles.Length).tostring('P')) of the files in a sample of $($sourceFiles.Length) are having accurate time stamps--------------`n"+$output;
Add-Content $logFile $output;
}

function checkDiskFree{
<#
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="localhost"
$thisVolume=$sourceVolume

# 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 expandZipfile($file, $destination){
$shell = new-object -com shell.application
$zip = $shell.NameSpace($file)

foreach($item in $zip.items()){
$shell.Namespace($destination).copyhere($item)
}
}

function installEmcopy{
$emcopyIsInstalled=(Get-Command emcopy.exe -ErrorAction SilentlyContinue) # Deterministic check on whether emcopy is already available on this system
if (!($emcopyIsInstalled)){
$tempDir="C:\Temp";
$extractionDir="C:\Windows"
$source = "https://kimconnect.com/wp-content/uploads/2019/08/emcopy.zip";
$destinationFile = "$tempDir\emcopy.zip";
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
New-Item -ItemType Directory -Force -Path $tempDir
New-Item -ItemType Directory -Force -Path $extractionDir
$webclient = New-Object System.Net.WebClient;
$WebClient.DownloadFile($source,$destinationFile);
expandZipfile $destinationFile -Destination $extractionDir
}else{
"EMCOPY is currently available in this system.`n";
}
}

function setACL{
get-acl -Path $source | Set-Acl -Path $destination # This command requires PowerShell 5
}

function startEmcopy{
param(
[string]$sourceAndDestination
)
"Emcopy has started..."
try{
invoke-expression "emcopy.exe $sourceAndDestination $switches $log";
}
catch{
# Record any errors into log and continue to next item
continue;
}
"`r`nEmcopy process has finished. Log is now generated at: $log";
}

Function proceed{
# Start the timer
Add-Content $logFile "`n`n`n---------------------------------Job Started: $dateStamp---------------------------------";
$totalTime=0;
$stopWatch= [System.Diagnostics.Stopwatch]::StartNew()
"Powershell version detected: $($PSVersionTable.PSVersion.Major)`.$($PSVersionTable.PSVersion.Minor)"
if (validateSourceAndDestination $block){
translateSource $source;
#"Shadow Copy source: $tranlatedSource`nDestination: $destination";
$translatedBlock="$translatedSource $destination";
if (checkDiskFree){
createShadow $sourceVolume;
installEmcopy;
startEmcopy $translatedBlock;
deleteShadow;
sampleTimeStamp;
"Program is completed."
"Log for this activity has been generated at $logFile"
}else{"Not enough disk space to create a VSS snapshot.`r`Program is aborted."}
}

# Getting storage estimate
$storage=(Get-ChildItem $source -Recurse | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1GB;
$storageDestination=(Get-ChildItem $destination -Recurse | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1GB;

# Stop the timer
#$elapsedSeconds=$stopWatch.Elapsed.TotalSeconds;
$totalTime=$stopWatch.Elapsed.TotalSeconds;
#$elapsedDisplay=([timespan]::fromseconds($elapsedSeconds)).ToString().Split('.')[0]
#$totalTime+=$elapsedSeconds;

# Add total time display to log
$timeDisplay = ([timespan]::fromseconds($totalTime)).ToString()
Add-Content $logFile "`r`n------------------------------Total Time Elapsed: $timeDisplay------------------------------";

# Record overall speed to log
$speed=$storage/($totalTime/360)
Add-Content $logFile "`r`n---------------------------Aggregate speed: $speed GB per hour---------------------------";

# Compare storage results
Add-Content $logFile "`r`n---------------------------Comparing: Source $storage GB vs Destination $storageDestination GB---------------------------";
}

proceed;
################################## Main Programming Sequence ####################################

# cmd /c pause | out-null;
Version 0.14
<#
.Description: File_Copy_Script Version: 0.14

Purpose: this PowerShell Script is to efficiently mirror large batches of files using Robocopy in conjunction with Volume Shadow Services

Current Features:
1. Check for any errors on the Sources or Destinations and generate a report of any extra spaces in UNC
2. Create a snapshot of the source volume using Shadow Copy to capture any locked files
3. Execute robocopy to mirror the source to its corresponding destination with a time stamp variance allowance of 2 seconds for speed and resiliency
4. Sample the copied files to 'spot check' any time stamp variances

Features planned for development:
5. Execute in the context of a Domain Administrator
6. Enable Volume Shadow Copy (VSS) at Source machines if it has been disabled, and reverse the action when done with copying
7. Trigger Remote Powershell to launch execution from a middle server (a "jump box" that is not a source nor destination)
if the provided Source is detected as a Universal Naming Convention (UNC) path instead of a local file system (LFS) path

Limitations:
- Require robocopy version XP016 or higher
- Only works if the source is LFS and destination is UNC
#>


$source="C:\Users\yamama\Desktop\test" # LFS path, only
$destination="\\FILESHERVER01\test" # UNC path, only
$block="$source $destination"

$switches="/MIR /SEC /DCOPY:T /R:0 /W:0 /XO /FFT /TBD /NP "
$dateStamp = Get-Date -Format "yyyy-MM-dd-hhmmss"
$scriptName=$MyInvocation.MyCommand.Path
$scriptPath=Split-Path -Path $scriptName
$logPath="$scriptPath\robocopy_logs"
$logFile="$logPath\robocopy-log-$dateStamp.txt"
$log="/LOG+:$logFile"
$lockedFilesReport="$logPath\_locked-files-log-$dateStamp.txt"
$pathErrorsLog="$logPath`\_path-errors-log-$dateStamp.txt"
$sampleSize=1000;
$GLOBAL:shadowMount="C:\shadowcopy"

# 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
}

# Run your code that needs to be elevated here
Write-Host -NoNewLine "Now Running as Administrator..."
#$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

New-Item -ItemType Directory -Force -Path $logpath;
"Executing copying tasks..."

function createShadow(){
[cmdletbinding()]
param(
[string]$targetVolume="C:\"
)
if (!($targetVolume -like "*\")){$targetVolume+="\"}
$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 + "\"
cmd /c mklink /d $shadowMount $thisShadowPath
"Shadow of $targetVolume has been made and it's accessible at this local file system (LFS): $shadowMount."
#copyLockedFiles; # this function is to be developed: retrieve lock files list, copy each item on list
#deleteShadow $thisShadow $thisShadowMount

# Export variables
$GLOBAL:shadow=$thisShadow;
}

function deleteShadow(){
# Remove symlink
(Get-Item $shadowMount).Delete()

# delete single instance of volume snapshots
$shadow.Delete()

# Delete all instances of volume snapshots
#Get-WmiObject Win32_ShadowCopy | % {$_.delete()}

"Shadow link $shadowMount has been removed."
}

function logPathError($pathError){
Add-Content $pathErrorsLog "$pathError";
}

function validateDirectory($dirToValidate){
if(Test-Path -Path $dirToValidate){return $True}
else{return $False;}
}

function createDirectory($dir){
# Create folder if it doesn't exist
if(!(validateDirectory $dir)){
New-Item -path $dir -type directory
}
}

function validateSourceAndDestination($thisblock){
$spacesCount=($thisblock.Split(' ')).Count-1
if ($spacesCount -eq 1){
$GLOBAL:source,$GLOBAL:destination=$thisblock.split(' ');
$sourceTest=validateDirectory $source
$destinationTest=validateDirectory $destination
if ($sourceTest -and $destinationTest){
return $True;
}
else {
if (!($sourceTest)){
logPathError "Source: $source";
return $False;
}
if (!($destinationTest)){
$createDestinationPath=(Read-Host -Prompt "Destination: $destination does not exist.`nType 'y' or 'yes' to create.");
if ($createDestinationPath -like 'yes' -or $createDestinationPath -like 'y'){
createDirectory $destination;
return $True;
}
else{
logPathError "Destination: $destination";
return $False
}
}
return $False;
}
}
else {
logPathError $thisblock;
return $False;
}
}

function translateSource{
param(
[string]$uncPath
)
$uri = new-object System.Uri($uncPath)
$thisLocalPath=$uri.LocalPath
#$thisHost=$uri.Host
$GLOBAL:sourceVolume="$((Get-Item $thisLocalPath).PSDrive.Name)`:"
$GLOBAL:translatedSource=$thisLocalPath -replace "$sourceVolume", $shadowMount
}

function reportLockedFiles($unc){
$files=Get-ChildItem -Path $unc -Recurse | where { ! $_.PSIsContainer }| % {$_.FullName}
#$x=$files[0]
#simulateLockedFile "$x"
$lockedFiles=$files | % { if(isFileLocked $_){$_; } }
if ($lockedFiles){
createDirectory $logPath;
Add-Content $lockedFilesReport $lockedFiles;
}
#$lockedFile.Close();
}

function startRobocopy{
param(
[string]$sourceAndDestination
)
Add-Content $logFile "`n`n`n---------------------------------Job Started: $dateStamp---------------------------------";
$totalTime=0;
"Robocopy has started..."
$stopWatch= [System.Diagnostics.Stopwatch]::StartNew()
try{
invoke-expression "robocopy $sourceAndDestination $switches $log | Out-Null";
#invoke-expression "robocopy $item $switches $log | Out-Null";
}
catch{
# Record any errors into log and continue to next item
continue;
}
#"Checking locked files in $source..."
#reportLockedFiles $source
$elapsedSeconds=$stopWatch.Elapsed.TotalSeconds;
$elapsedDisplay=([timespan]::fromseconds($elapsedSeconds)).ToString().Split('.')[0]
#Add-Content $logFile "Block Time Elapsed: $elapsedDisplay";
$totalTime+=$elapsedSeconds;
"`r`nRobocopy is done."
$timeDisplay = ([timespan]::fromseconds($totalTime)).ToString()
#$timeDisplay = $timeString.substring(0, $timeString.lastIndexOf("."));
Add-Content $logFile "`r`n------------------------------Total Time Elapsed: $timeDisplay------------------------------";
}

function sampleTimeStamp{
$sourceFiles=Get-ChildItem $source -recurse | ? { !($_.PsIsContainer -and $_.FullName -notmatch 'archive') } | get-random -count $sampleSize | % {$_.FullName}
$commonDenominatingPaths=$sourceFiles | %{$_.replace($source,'')}
$destinationFiles=$commonDenominatingPaths | %{"$destination"+"$_";}
$badStamps=0;

"Checking a sample of $sampleSize for any time stamp inaccuracy..."

for ($i=0;$i -lt $sourceFiles.length;$i++){
$sourceFile=$sourceFiles[$i];
$destinationFile=$destinationFiles[$i];
$sourceTimeStamp=(gi $sourceFile).LastWriteTime;
$destinationTimeStamp=(gi $destinationFile).LastWriteTime;
if ($sourceTimeStamp -eq $destinationTimeStamp){
#$output+="`r`n$destinationFile is GOOD";
}
else {
$output+="`r`n$destinationFile is BAD";
$badStamps++;
}
}
$output="`r`n`r`n------------$((($sourceFiles.Length-$badStamps)/$sourceFiles.Length).tostring('P')) of the files in a sample of $($sourceFiles.Length) are having accurate time stamps--------------`n"+$output;
Add-Content $logFile $output;
}

function checkDiskFree{
<#
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="localhost"
$thisVolume=$sourceVolume

# 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 proceed{
if (validateSourceAndDestination $block){
translateSource $source;
#"Shadow Copy source: $tranlatedSource`nDestination: $destination";
$translatedBlock="$translatedSource $destination";
if (checkDiskFree){
createShadow $sourceVolume;
startRobocopy $translatedBlock;
deleteShadow;
sampleTimeStamp;
"Program is completed."
"Log for this activity has been generated at $logFile"
}else{"Not enough disk space to create a VSS snapshot.`r`Program is aborted."}
}
}

proceed;
pause;

Securing Windows Remote Desktop Services

secpol.msc > Local Policies > User Rights Assignments > double-click “Allow Log on through Remote Desktop Services” > remove Administrators and Remote Desktop Users > Add a customized group and/or users

gpedit.msc > Computer Configuration > Adminstrative Templates > Windows Components > Remote Desktop Services > Remote Desktop Session host > security > change these settings:
– Set client encryption level = High
– Require secure RPC communication = Enabled
– Require use of specific security layer for remote (RDP) connections = SSL
– Require user authentication for remote connections by using Network Level Authentication = Enabled

PowerShell: Convert Between Various SSL Certificate Formats

# Install Choco (look for instructions in this blog)

# Install openssl.light
choco install openssl.light -y
cd "C:\Program Files\OpenSSL\bin"

# Convert PEM to DER
openssl x509 -outform der -in certificate.pem -out certificate.der

# Convert PEM to P7B
openssl crl2pkcs7 -nocrl -certfile certificate.cer -out certificate.p7b -certfile CACert.cer

# Convert PEM to PFX
openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt -certfile CACert.crt

# Convert DER to PEM
openssl x509 -inform der -in certificate.cer -out certificate.pem

# Convert P7B to PEM
openssl pkcs7 -print_certs -in certificate.p7b -out certificate.cer

# Convert P7B to PFX
openssl pkcs7 -print_certs -in certificate.p7b -out certificate.cer
# OR
openssl pkcs12 -export -in certificate.cer -inkey privateKey.key -out certificate.pfx -certfile CACert.cer

# Convert PFX to PEM
openssl pkcs12 -in certificate.pfx -out certificate.p12 -nodes

# Convert PFX to P12
openssl pkcs12 -in certificate.pfx -out certificate.cer -nodes

# Extract Key
openssl pkcs12 -in C:\Users\rambo\Desktop\vector.pfx -nocerts -out C:\Users\rambo\Desktop\key.pem -nodes

# Example:
PS C:\Program Files\OpenSSL\bin> .\openssl pkcs12 -in C:\Users\Test\Desktop\star_kimconnect_com.pfx -out C:\Users\Test\Desktop\star_kimconnect_com.cer -nodes
Enter Import Password:
# Done

PowerShell: Discover Domains and Trusts in Microsoft Windows Environment

PS C:\Windows\system32> Get-ADTrust -Filter *
Direction : BiDirectional
DisallowTransivity : False
DistinguishedName : CN=kimconnectschools.org,CN=System,DC=kimconnect,DC=k12,DC=ca,DC=us
ForestTransitive : True
IntraForest : False
IsTreeParent : False
IsTreeRoot : False
Name : kimconnectschools.org
ObjectClass : trustedDomain
ObjectGUID : ad578513-3498-4c7c-a885-d5717c5fb313
SelectiveAuthentication : False
SIDFilteringForestAware : False
SIDFilteringQuarantined : False
Source : DC=kimconnect,DC=k12,DC=ca,DC=us
Target : kimconnectschools.org
TGTDelegation : False
TrustAttributes : 8
TrustedPolicy :
TrustingPolicy :
TrustType : Uplevel
UplevelOnly : False
UsesAESKeys : False
UsesRC4Encryption : False

PS C:\Windows\system32> get-addomain kimconnectschools.org
AllowedDNSSuffixes : {}
ChildDomains : {}
ComputersContainer : OU=_UnclaimedDomainJoined,OU=Sites,DC=kimconnectschools,DC=org
DeletedObjectsContainer : CN=Deleted Objects,DC=kimconnectschools,DC=org
DistinguishedName : DC=kimconnectschools,DC=org
DNSRoot : kimconnectschools.org
DomainControllersContainer : OU=Domain Controllers,DC=kimconnectschools,DC=org
DomainMode : Windows2008R2Domain
DomainSID : S-1-5-21-1846739454-297172624-1433393011
ForeignSecurityPrincipalsContainer : CN=ForeignSecurityPrincipals,DC=kimconnectschools,DC=org
Forest : kimconnectschools.org
InfrastructureMaster : kimconnect-DC1.kimconnectschools.org
LastLogonReplicationInterval :
LinkedGroupPolicyObjects : {CN={2DADDEE5-D6EF-41C6-9527-9DDC67A12CC7},CN=Policies,CN=System,DC=kimconnectschools,DC
=org, CN={31B2F340-016D-11D2-945F-00C04FB984F9},CN=Policies,CN=System,DC=kimconnectschoo
ls,DC=org}
LostAndFoundContainer : CN=LostAndFound,DC=kimconnectschools,DC=org
ManagedBy :
Name : kimconnectschools
NetBIOSName : kimconnectSCHOOLS
ObjectClass : domainDNS
ObjectGUID : fc7b46c2-175c-4ef7-96f4-afcdf27caf94
ParentDomain :
PDCEmulator : kimconnect-DC1.kimconnectschools.org
QuotasContainer : CN=NTDS Quotas,DC=kimconnectschools,DC=org
ReadOnlyReplicaDirectoryServers : {}
ReplicaDirectoryServers : {kimconnect-DC1.kimconnectschools.org, kimconnect-DC2.kimconnectschools.org}
RIDMaster : kimconnect-DC1.kimconnectschools.org
SubordinateReferences : {DC=DomainDnsZones,DC=kimconnectschools,DC=org,
DC=ForestDnsZones,DC=kimconnectschools,DC=org, CN=Configuration,DC=kimconnectschools,DC=org}
SystemsContainer : CN=System,DC=kimconnectschools,DC=org
UsersContainer : CN=Users,DC=kimconnectschools,DC=org

PowerShell: Install Windows Cluster Admin

# Install Microsoft Clustering Management
Function installClusteringManagment{
# Set PowerShell Gallery as Trusted to bypass prompts
$trustPSGallery=(Get-psrepository -Name 'PSGallery').InstallationPolicy
If($trustPSGallery -ne 'Trusted'){
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
}
# Adding Microsoft Cluster
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;
}
}

installClusteringManagment;
Cluadmin.msc;

Windows 2012 Maintenance

– Check if .NET Framework 4.5 is installed: (Get-ItemProperty -Path 'HKLM:\Software\Microsoft\NET Framework Setup\NDP\v4\Full' -ErrorAction SilentlyContinue).Version -like '4.5*'
– Install .NET Framework 4.7.2: https://www.microsoft.com/en-us/download/details.aspx?id=55167
– Install Windows Management Framework 5.1: https://www.microsoft.com/en-us/download/details.aspx?id=54616
– Perform Windows Update using this script

PowerShell: Discover Environment

Discovery of Active Directory Computers

# Scan for all Windows machines in Active Directory
PS C:\Windows\system32> Get-ADComputer -Filter "OperatingSystem -like 'Windows*'" -Properties OperatingSystem | group operatingsystem | sort name

Count Name Group
----- ---- -----
270 Windows 10 Enterprise
1 Windows 10 Enterprise ...
18 Windows 10 Enterprise N
110 Windows 10 Pro
1 Windows 10 Pro Education
45 Windows 7 Enterprise
49 Windows 7 Professional
1 Windows 7 Ultimate
1 Windows 8 Pro
168 Windows 8.1 Enterprise
13 Windows 8.1 Pro
1 Windows Embedded Standard
2 Windows Server 2003
1 Windows Server 2008 R2...
5 Windows Server 2008 R2...
6 Windows Server 2008 R2...
8 Windows Server 2012 Da...
75 Windows Server 2012 R2...
15 Windows Server 2012 R2...
9 Windows Server 2016 Da...
16 Windows Server 2016 St...
13 Windows Server 2019 Da...
23 Windows XP Professional

# Narrow down to systems requiring removal such as Windows 2003 & XP
$win2003Servers=(Get-ADComputer -Filter "OperatingSystem -like 'Windows Server 2003'" -Properties OperatingSystem).DNSHostname
$winXPMachines=(Get-ADComputer -Filter "OperatingSystem -like 'Windows XP*'" -Properties OperatingSystem).DNSHostname

Discovery of Computer Resources

# Purpose: this script uses a list of computer names to discover information about each of them (IPs, Machine Type, CPU, Memory, and Disks)

$computerNames=@(
'SHERVER01',
'SHERVER03',
'SHERVER03'
)

$GLOBAL:computerObjects=@()

$scriptName=$MyInvocation.MyCommand.Path
$scriptPath=Split-Path -Path $scriptName

function systemsDiscovery{
#for ($i=0;$i -le $computers.length;$i++){
foreach($name in $computerNames){
"Scanning $name ..."
$computerObject=New-Object PSObject
$ips="Unknown";
$machineType="Unknown";
$cpu="Unknown";
$cpuLoad=0;
$memory="Unknown";
$volumes="Unknown";
try{
$ips=[System.Net.Dns]::GetHostAddresses($name)|?{$_.AddressFamily -eq "InterNetwork"}|%{$_.IPAddressToString;}
$machineModel=(Get-WmiObject -Class Win32_ComputerSystem -ComputerName $name -ea stop).Model
$cpu = Get-WmiObject -computername $name win32_processor -ea stop |select Name,NumberOfCores
$cpuLoad = Get-WmiObject -computername $name win32_processor -ea stop| Measure-Object -property LoadPercentage -Average | Select @{Name="CurrentLoad";Expression={"{0:N2} %" -f ($_.Average)}}
$memory = gwmi -Class win32_operatingsystem -computername $name -ea stop| select @{Name="Memory";Expression={"{0:N2} GB" -f ($_.TotalVisibleMemorySize / 1048576)}},@{Name = "MemoryUsage"; Expression = {"{0:N2} %" -f ((($_.TotalVisibleMemorySize - $_.FreePhysicalMemory)*100)/ $_.TotalVisibleMemorySize) }}
#$volumes = (gwmi -Class win32_volume -computername $name -ea stop| ?{$_.DriveLetter -ne $isnull}|Select-object DriveLetter,Capacity,@{Name = "PercentFree"; Expression = {"{0:N2}" -f (($_.FreeSpace / $_.Capacity)*100) } } | Out-String ).TrimEnd()
$volumes = (gwmi -Class win32_volume -computername $name -Filter "DriveType!=5" -ea stop| ?{$_.DriveLetter -ne $isnull}|Select-object DriveLetter,@{Name="Capacity";Expression={"{0:N2} GiB" -f ($_.Capacity/1073741824)}},@{Name = "PercentFree"; Expression = {"{0:N2} %" -f (($_.FreeSpace / $_.Capacity)*100) } } | Out-String ).Trim()
}
catch{
$machineModel="Unknown";
$machineType="Unknown";
if(!($ips)){$ips="Unknown";}
if(!($cpu)){$cpu="Unknown";}
if(!($memory)){$memory="Unknown";}
if(!($volumes)){$volumes="Unknown";}
Continue;
}
finally{
switch -wildcard ($machineModel){
"*Virtual*" {$machineType="VMWare Virtual Machine";}
"*HVM*" {$machineType="AWS Virtual Machine";}
"Unknown" {$machineType="Physical Machine";}
}
$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 "cpu" -Value $cpu,$cpuLoad
$computerObject | Add-Member -MemberType NoteProperty -Name "memory" -Value $memory
$computerObject | Add-Member -MemberType NoteProperty -Name "volumes" -Value $volumes
$computerObject
$GLOBAL:computerObjects+=,$computerObject
}
}
}

systemsDiscovery;

"Exporting results to a CSV file in the current path of $scriptPath`..."
$computerObjects|Export-Csv -Path "$scriptPath`\systemsDiscovery.csv" -NoTypeInformation