Deep Audit: Listing Nested Active Directory Group Members via VBScript | Lazy Admin Blog

Have you ever looked at a “Domain Admins” group and thought it looked suspiciously small? The culprit is usually nesting. Standard AD queries often fail to “recurse,” meaning they show you the subgroup but not the people inside it.
This script, ListGroupMembers_IncludingNested.vbs, uses a recursive function to dive into every sub-group and extract the actual users, ensuring your security audits are 100% accurate.
The Script: How it Works
The script utilizes a Dictionary Object to keep track of groups it has already scanned. This is a critical “Lazy Admin” safety featureโit prevents the script from getting stuck in an infinite loop if two groups are members of each other.
Usage Instructions
- Copy the code below into Notepad.
- Edit the
StrGroupNamevariable to match your target group. - Save the file as
ListGroupMembers.vbs. - Run it from the command prompt using
cscript ListGroupMembers.vbs.
' -- Save as ListGroupMembers_IncludingNested.vbsOption ExplicitDim ObjRootDSE, ObjConn, ObjRS, ObjCustomDim StrDomainName, StrGroupName, StrSQL, StrGroupDN, StrEmptySpaceSet ObjRootDSE = GetObject("LDAP://RootDSE")StrDomainName = Trim(ObjRootDSE.Get("DefaultNamingContext"))' -- Edit the line below with your Group NameStrGroupName = "YourGroupNameHere" StrSQL = "Select ADsPath From 'LDAP://" & StrDomainName & "' Where ObjectCategory = 'Group' AND Name = '" & StrGroupName & "'"Set ObjConn = CreateObject("ADODB.Connection")ObjConn.Provider = "ADsDSOObject": ObjConn.Open "Active Directory Provider"Set ObjRS = ObjConn.Execute(StrSQL)If ObjRS.EOF Then WScript.Echo "Group not found: " & StrGroupNameElse StrGroupDN = Trim(ObjRS.Fields("ADsPath").Value) Set ObjCustom = CreateObject("Scripting.Dictionary") GetAllNestedMembers StrGroupDN, " ", ObjCustomEnd If
Why VBScript in 2026?
While PowerShell is the modern standard, many legacy environments and automated scheduled tasks still rely on VBScript because it requires zero execution policy changes and runs natively on every Windows machine since Server 2000. It is the “Old Reliable” of the AD world.
Key Features of this Script
- Recursive Discovery: It doesn’t just stop at the first layer.
- Class Identification: Clearly marks if a member is a
User,Computer, or anotherGroup. - Loop Protection: Uses the
Scripting.Dictionaryto escape circular nesting traps.
#ActiveDirectory #WindowsServer #CyberSecurity #SysAdmin #ITAudit #VBScript #Automation #LazyAdmin #TechArchive
The Ultimate Server Audit: Deep-Dive Inventory to Excel (VBScript) | Lazy Admin Blog

If you are facing a massive compliance audit or a data center migration, “basic” info isn’t enough. You need to know exactly what is under the hood: What roles are active? How much disk space is actually left? What random software was installed three years ago?
This VBScript is a one-stop-shop. It checks network connectivity and then scrapes WMI and the Registry to build a massive, multi-column Excel report.
What this Script Collects:
- Hardware: Manufacturer, Model, CPU Type, and RAM (converted to GB).
- OS Details: Version, Caption, and the exact Installation Date.
- Storage: Total Size vs. Free Space (with a Red-Alert highlight if space is < 20%).
- Network: DHCP status, IP, Subnet, and Gateway.
- Software & Roles: Every Windows Server Role/Feature and every application listed in the Registry’s Uninstall key (including version and install date).
Preparation
- Directory: Create
C:\Tempon your local machine. - Input: Create a file named
ServerList.txtinC:\Tempwith your server names (one per line). - Excel: Ensure Microsoft Excel is installed.
The Script: Server_Inventory.vbs
' Save as Server_Inventory.vbs in C:\Temp' lazyadminblog.com - Ultimate Inventory ScriptOn Error Resume Next dtmDate = DatestrMonth = Month(Date)strDay = Day(Date)strYear = Right(Year(Date),2)strFileName = "C:\Temp\ServerInventory_" & strMonth & "-" & strDay & "-" & strYear & ".xls"Set objExcel = CreateObject("Excel.Application") objExcel.Visible = True objExcel.Workbooks.Add Set fso1 = CreateObject("Scripting.FileSystemObject") Set pcfile = fso1.OpenTextFile("C:\Temp\ServerList.txt",1) Wscript.Echo "Audit in progress... Please wait!"'--- Setup Header Row ---Sub SetupHeader(col, text) objExcel.Cells(1, col).Value = text objExcel.Cells(1, col).Font.Colorindex = 2 objExcel.Cells(1, col).Font.Bold = True objExcel.Cells(1, col).Interior.ColorIndex = 23 objExcel.Cells(1, col).Alignment = -4108 ' CenterEnd SubSetupHeader 1, "Computer Name"SetupHeader 2, "Manufacturer"SetupHeader 3, "Model"SetupHeader 4, "RAM (GB)"SetupHeader 5, "Operating System"SetupHeader 6, "Installed Date"SetupHeader 7, "Processor"SetupHeader 8, "Drive"SetupHeader 9, "Drive Size (GB)"SetupHeader 10, "Free Space (GB)"SetupHeader 11, "Adapter Description"SetupHeader 12, "DHCP Enabled"SetupHeader 13, "IP Address"SetupHeader 14, "Subnet"SetupHeader 15, "Gateway"SetupHeader 16, "Roles & Features"SetupHeader 17, "Installed Software"SetupHeader 18, "Install Date"SetupHeader 19, "Version"SetupHeader 20, "Size"y = 2 Do While Not pcfile.AtEndOfStream computerName = pcfile.ReadLine Err.Clear Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & computerName & "\root\cimv2") If Err.Number = 0 Then ' Fetch Queries Set colSettings = objWMIService.ExecQuery("SELECT * FROM Win32_ComputerSystem") Set colOSSettings = objWMIService.ExecQuery("SELECT * FROM Win32_OperatingSystem") Set colProcSettings = objWMIService.ExecQuery("SELECT * FROM Win32_Processor") Set colDiskSettings = objWMIService.ExecQuery("Select * from Win32_LogicalDisk Where DriveType=3") Set colAdapters = objWMIService.ExecQuery("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True") For Each objComputer In colSettings strManufacturer = objComputer.Manufacturer strModel = objComputer.Model strRAM = FormatNumber((objComputer.TotalPhysicalMemory / (1024^3)), 2) For Each objOS In colOSSettings strOS = objOS.Caption OSinstDate = CDate(Mid(objOS.InstallDate,1,4)+"/"+Mid(objOS.InstallDate,5,2)+"/"+Mid(objOS.InstallDate,7,2)) For Each objProc In colProcSettings strProc = objProc.Name ' Populate Static Info objExcel.Cells(y, 1).Value = computerName objExcel.Cells(y, 2).Value = strManufacturer objExcel.Cells(y, 3).Value = strModel objExcel.Cells(y, 4).Value = strRAM objExcel.Cells(y, 5).Value = strOS objExcel.Cells(y, 6).Value = OSinstDate objExcel.Cells(y, 7).Value = strProc ' Drive Logic a = y For Each objDisk In colDiskSettings objExcel.Cells(a, 8).Value = objDisk.DeviceID sz = objDisk.Size / (1024^3) fr = objDisk.FreeSpace / (1024^3) objExcel.Cells(a, 9).Value = FormatNumber(sz, 2) objExcel.Cells(a, 10).Value = FormatNumber(fr, 2) If fr < (sz * 0.2) Then objExcel.Cells(a, 10).Interior.ColorIndex = 3 ' Low Space Alert a = a + 1 Next ' Network Logic b = y For Each objAdapter In colAdapters objExcel.Cells(b, 11).Value = objAdapter.Description objExcel.Cells(b, 12).Value = objAdapter.DHCPEnabled If Not IsNull(objAdapter.IPAddress) Then objExcel.Cells(b, 13).Value = objAdapter.IPAddress(0) If Not IsNull(objAdapter.IPSubnet) Then objExcel.Cells(b, 14).Value = objAdapter.IPSubnet(0) If Not IsNull(objAdapter.DefaultIPGateway) Then objExcel.Cells(b, 15).Value = objAdapter.DefaultIPGateway(0) b = b + 1 Next ' Roles & Features x = y Set colRoleFeatures = objWMIService.ExecQuery("Select * from Win32_ServerFeature") If colRoleFeatures.Count > 0 Then For Each objRole In colRoleFeatures objExcel.Cells(x, 16).Value = objRole.Name x = x + 1 Next Else objExcel.Cells(x, 16).Value = "None Found" End If ' Software Registry Scan s = y Const HKLM = &H80000002 strKey = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" Set objReg = GetObject("winmgmts://" & computerName & "/root/default:StdRegProv") objReg.EnumKey HKLM, strKey, arrSubkeys For Each strSubkey In arrSubkeys objReg.GetStringValue HKLM, strKey & strSubkey, "DisplayName", strVal1 If strVal1 <> "" Then objExcel.Cells(s, 17).Value = strVal1 objReg.GetStringValue HKLM, strKey & strSubkey, "InstallDate", strVal2 objExcel.Cells(s, 18).Value = strVal2 objReg.GetDWORDValue HKLM, strKey & strSubkey, "VersionMajor", vMaj objReg.GetDWORDValue HKLM, strKey & strSubkey, "VersionMinor", vMin objExcel.Cells(s, 19).Value = vMaj & "." & vMin s = s + 1 End If Next ' Advance Row to next available empty spot y = a If b > y Then y = b If x > y Then y = x If s > y Then y = s y = y + 1 ' Buffer line Next Next Next Else objExcel.Cells(y, 1).Value = computerName objExcel.Cells(y, 2).Value = "OFFLINE" objExcel.Cells(y, 2).Interior.ColorIndex = 3 y = y + 1 End If Loop ' Final FormattingFor col = 1 To 20: objExcel.Columns(col).AutoFit: NextobjExcel.ActiveWorkbook.SaveAs strFileNameWscript.Echo "Complete! Report saved to " & strFileName
Why itโs a Game Changer
- The “Red Flag” Feature: It automatically highlights any disk with less than 20% free space in Red. This instantly tells you which servers need urgent cleanup.
- Software Archeology: Most scripts skip software lists because they are messy. This script pulls directly from the
Uninstallregistry keys, capturing even the apps that don’t show up in standard WMI queries. - Intelligent Row Management: Because software, roles, and disks all have different counts, the script calculates the “max row” used for each server and jumps to the next clear space for the next machine.
Stop Hunting for Web Servers: How to Auto-Discover Every IIS Instance in Your Domain | Lazy Admin Blog

Have you ever been asked for a list of every active web server in your environment, only to realize your documentation is six months out of date? You could check your DNS records manually, or you could let PowerShell do the detective work for you.
This script scans your Active Directory for Windows Servers, checks if the World Wide Web Publishing Service (W3SVC) is actually running, and then pulls a deep-profile of the hardware, OS, and network configuration for every active hit.
The Setup
- Create the workspace: Create a folder at
C:\Temp\ServersRunningIIS. - Prepare the list: The script will automatically generate a list of all Windows Servers from AD, but ensure you have the Active Directory PowerShell module installed.
- Run with Privileges: Since the script uses WMI to query remote system info (RAM, OS Version, etc.), run your PowerShell ISE or Console as a Domain Admin.
The PowerShell Script
# Script: IIS Server Discovery & Profiler# Location: lazyadminblog.com# Purpose: Identify active IIS nodes and collect hardware/OS specsImport-Module ActiveDirectory# 1. Harvest all Windows Servers from ADWrite-Host "Gathering server list from Active Directory..." -ForegroundColor Cyan$servers = Get-ADComputer -Filter {operatingsystem -Like "Windows server*"} | Select-Object -ExpandProperty Name$servers | Out-File "C:\Temp\ServersRunningIIS\serverlist.txt"# 2. Load the list for processing$serversall = Get-Content "C:\Temp\ServersRunningIIS\serverlist.txt" Start-Transcript -Path "C:\Temp\ServersRunningIIS\log_output.txt" -Appendforeach($vm in $serversall) { try { # Check if IIS Service (W3SVC) exists and is running $iis = Get-WmiObject Win32_Service -ComputerName $vm -Filter "name='W3SVC'" -ErrorAction SilentlyContinue if($iis.State -eq "Running") { Write-Host "FOUND: IIS is active on $vm" -BackgroundColor DarkBlue -ForegroundColor DarkYellow # Collect Network Info $ipinfo = Get-WmiObject Win32_NetworkAdapterConfiguration -ComputerName $vm | Where-Object {$_.IPEnabled -eq $true -and $_.IPAddress -like "1*"} | Select-Object -First 1 # Collect Hardware Info $hwinfo = Get-WmiObject Win32_Computersystem -ComputerName $vm # Collect OS Info $osinfo = Get-WmiObject Win32_OperatingSystem -ComputerName $vm # Flattening data for CSV-style output $allinfo = "$($hwinfo.Name);$($hwinfo.Domain);$($ipinfo.IPAddress);$($ipinfo.IPSubnet);$($ipinfo.DefaultIPGateway);$($hwinfo.TotalPhysicalMemory);$($hwinfo.Manufacturer);$($hwinfo.Model);$($osinfo.Caption);$($osinfo.OSArchitecture);$($osinfo.ServicePackMajorVersion);$($osinfo.SystemDrive);$($osinfo.Version)" # Save results to our 'Running' list $allinfo | Out-File "C:\Temp\ServersRunningIIS\RunningWebServers.txt" -Append } } catch { Write-Host "Could not connect to $vm" -ForegroundColor Red }}Stop-TranscriptWrite-Host "Audit Complete! Check C:\Temp\ServersRunningIIS\RunningWebServers.txt" -ForegroundColor Green
Whatโs inside the report?
The output file (RunningWebServers.txt) uses a semicolon (;) delimiter, making it easy to import into Excel. It captures:
- Network: IP Address, Subnet, and Gateway.
- Hardware: Manufacturer, Model, RAM, and Domain membership.
- Software: OS Version, Architecture (x64/x86), and System Drive.
Lazy Admin Tip
If you want to open the results immediately in Excel, just rename the output file from .txt to .csv and use the “Text to Columns” feature in Excel with the semicolon as the separator!
From Zero to Complete IP Inventory in 5 Seconds: The Multi-Host VBScript | Lazy Admin Blog

Manually documenting IP addresses, MACs, and DNS settings is the definition of “busy work.” This VBScript automates the entire process. It reads a list of servers from a text file, queries each one via WMI, and builds a professional Excel report in real-time.
How to Use This Script
- Prepare the Input: Create a text file (e.g.,
servers.txt) and list your hostnames or IP addresses, one per line. - Save the Script: Save the code below as
IPAddressInventory.vbs. - Run: Double-click the
.vbsfile. When prompted, provide the full path to your text file (e.g.,C:\Scripts\servers.txt). - Requirement: You must have Microsoft Excel installed on the machine where you run the script.
The VBScript Code
VBScript
' Save as IPAddressInventory.vbs' Input: Text file with Hostnames/IPs' Output: Excel Spreadsheet (IP_Addresses.xlsx)On Error Resume NextConst FOR_READING = 1'--- File Input ---strSrvListFile = InputBox ("Please enter the server list file path OR UNC file path" & vbCrLf & "Eg: C:\Scripts\server.txt" & vbCrLf & "Eg: \\servername\scripts\server.txt","File Input location")Set objFSO = CreateObject ("Scripting.FileSystemObject")Set objReadFile = objFSO.OpenTextFile (strSrvListFile, FOR_READING)'--- File Output ---strOutput = objfso.GetParentFolderName(strSrvListFile) &"\"'--- Error Handling ---If Err.Number <> 0 Then WScript.Echo "Please Enter a Valid file Name" Err.Clear WScript.QuitEnd If'--- Excel Object Creation ---Set objExcel = CreateObject ("Excel.application")objExcel.Visible = TrueSet objWorkbook = objExcel.Workbooks.Add()Set objWorksheet = objWorkbook.Worksheets("Sheet1")x = 1y = 1'--- Define Headers ---objWorksheet.Cells (x, y).value = "S.No"objWorksheet.Cells (x, y+1).value = "Server Name"objWorksheet.Cells (x, y+2).value = "Description"objWorksheet.Cells (x, y+3).value = "IP_Address"objWorksheet.Cells (x, y+4).value = "Subnet"objWorksheet.Cells (x, y+5).value = "MACAddress"objWorksheet.Cells (x, y+6).value = "Gateway" objWorksheet.Cells (x, y+7).value = "Preffered DNS"objWorksheet.Cells (x, y+8).value = "Primary DNS"objWorksheet.Cells (x, y+9).value = "Secondary DNS"objWorksheet.Cells (x, y+10).value = "Additional DNS 1"objWorksheet.Cells (x, y+11).value = "Additional DNS 2"objWorksheet.Cells (x, y+12).value = "WINS Primary"objWorksheet.Cells (x, y+13).value = "WINS Secondary"objWorksheet.Cells (x, y+14).value = "DNS Suffix"objWorksheet.Cells (x, y+15).value = "DNS Suffix Order"objWorksheet.Cells (x, y+16).value = "Remarks"s = 1Do Until objReadFile.AtEndOfStream k = 0 arrComputer = objReadFile.ReadLine strServer = Split (arrComputer, ",") objWorksheet.Cells (x+1, y).value = s objWorksheet.Cells (x+1, y+1).value = strServer(k) Set objWMIService = GetObject ("winmgmts:" & "!\\" & strServer(k) & "\root\cimv2") '--- Query Network Information --- If Err.Number = 0 Then WScript.Echo strServer(k) &": Inventoring" Set colAdapters = objWMIService.ExecQuery("Select * from Win32_NetworkAdapterConfiguration Where IPEnabled = True") For Each objAdapter in colAdapters objWorksheet.Cells(x+1, y+2).Value = objAdapter.Description ' IP Address Logic If Not IsNull(objAdapter.IPAddress) Then For i = LBound(objAdapter.IPAddress) To UBound(objAdapter.IPAddress) If Not InStr(objAdapter.IPAddress(i),":") > "0" Then objWorksheet.Cells(x+1, y+3).Value = objAdapter.IPAddress(i) End If Next End If ' Subnet Logic If Not IsNull(objAdapter.IPSubnet) Then For i = LBound(objAdapter.IPSubnet) To UBound(objAdapter.IPSubnet) If objAdapter.IPSubnet(i)<> "64" Then objWorksheet.Cells(x+1, y+4).Value = objAdapter.IPSubnet(i) End If Next End If objWorksheet.Cells(x+1, y+5).Value = objAdapter.MACAddress ' Gateway Logic If IsNull(objAdapter.DefaultIPGateway) Then objWorksheet.Cells(x+1, y+6).Value = "Gateway Not Set" Else For i = LBound(objAdapter.DefaultIPGateway) To UBound(objAdapter.DefaultIPGateway) objWorksheet.Cells(x+1, y+6).Value = objAdapter.DefaultIPGateway(i) Next End If ' DNS Logic If IsNull(objAdapter.DNSServerSearchOrder) Then objworksheet.Cells(x+1, y+7).Value = "DNS Not Set" Else For i = LBound(objAdapter.DNSServerSearchOrder) To UBound(objAdapter.DNSServerSearchOrder) objWorksheet.Cells(x+1, y+7).Value = objAdapter.DNSServerSearchOrder(i) y = y + 1 Next End If y = 1 objWorksheet.Cells(x+1, y+12).Value = objAdapter.WINSPrimaryServer objWorksheet.Cells(x+1, y+13).Value = objAdapter.WINSSecondaryServer objWorksheet.Cells(x+1, y+14).Value = objAdapter.DNSDomain ' Suffix Logic If IsNull(objAdapter.DNSDomainSuffixSearchOrder) Then objworksheet.Cells(x+1, y+14).Value = "Suffix Order NA" Else For i = LBound(objAdapter.DNSDomainSuffixSearchOrder) To UBound(objAdapter.DNSDomainSuffixSearchOrder) objWorksheet.Cells(x+1, y+15).Value = objAdapter.DNSDomainSuffixSearchOrder(i) x = x + 1 Next x = x - 1 End If x = x + 1 WScript.Echo strServer(k) &": Completed" Next Else ' Error Handling for Offline Servers objWorksheet.Cells(x+1, y+16).Value = Err.Number & "_" & Err.Description WScript.Echo strServer(k) &": "& Err.Description Err.Clear x = x + 1 End If s = s + 1Loop'--- Formatting and Saving ---Set objRange = objWorksheet.UsedRangeobjRange.EntireColumn.Autofit()objExcel.ActiveWorkbook.Saveas strOutput & "IP_Addresses.xlsx"MsgBox "Operation Completed Successfully " ,,"IP Address"
Key Features of the Script
- Automatic Excel Formatting: It uses
UsedRange.Autofit()to ensure the data is readable as soon as the file opens. - WMI Integration: It queries the
Win32_NetworkAdapterConfigurationclass directly from the remote machine. - Multi-Adapter Support: If a server has multiple enabled NICs, the script loops through each to capture all configurations.
- Remark Logging: If a machine is unreachable, the error code and description are written directly into the Excel “Remarks” column so you know which servers to check manually.

Azure Alert: Default Outbound Access Ends March 31st 2026 | Lazy Admin Blog

Is your “Internet-less” VM about to lose its connection? Here is the fix.
For years, Azure allowed Virtual Machines without an explicit outbound connection (like a Public IP or NAT Gateway) to “cheat” and access the internet using a default, hidden IP. That ends on March 31st 2026. If you haven’t transitioned your architecture, your updates will fail, your scripts will break, and your apps will go dark.
1. What exactly is changing?
Microsoft is moving toward a “Secure by Default” model. The “Default Outbound Access” (which was essentially a random IP assigned by Azure) is being retired. From now on, you must explicitly define how a VM talks to the outside world.
2. The Three “Lazy Admin” Solutions
You have three ways to fix this before the deadline. Choose the one that fits your budget and security needs:
Option A: The NAT Gateway (Recommended)
This is the most scalable way. You associate a NAT Gateway with your Subnet. All VMs in that subnet will share one (or more) static Public IPs for outbound traffic.
- Pro: Extremely reliable and handles thousands of concurrent sessions.
- Con: There is a small hourly cost + data processing fee.
Option B: Assign a Public IP to the VM
The simplest “Quick Fix.” Give the VM its own Standard Public IP.
- Pro: Immediate fix for a single server.
- Con: Itโs a security risk (opens a door into the VM) and gets expensive if you have 50 VMs.
Option C: Use a Load Balancer
If you already use an Azure Load Balancer, you can configure Outbound Rules.
- Pro: Professional, enterprise-grade setup.
- Con: More complex to configure if you’ve never done it before.
3. How to find your “At Risk” VMs
Don’t wait for March 31st 2026 to find out what’s broken. Run this PowerShell snippet to find VMs that might be relying on default outbound access:
# Find VMs without a Public IP in a specific Resource Group$VMs = Get-AzVM -ResourceGroupName "YourRGName"foreach ($vm in $VMs) { $nic = Get-AzNetworkInterface -ResourceId $vm.NetworkProfile.NetworkInterfaces[0].Id if ($null -eq $nic.IpConfigurations.PublicIpAddress) { Write-Host "Warning: $($vm.Name) has no Public IP and may rely on Default Outbound Access!" -ForegroundColor Yellow }}
๐ก๏ธ Lazy Admin Verdict:
If you have more than 3 VMs, deploy a NAT Gateway. Itโs the “Set and Forget” solution that ensures you won’t get a 2 AM call on April 1st when your servers can’t reach Windows Update.
M365 E7: The “Super SKU” is Here (And it Costs $99) | Lazy Admin Blog

Is the new ‘Frontier Suite’ a lazy admin’s dream or a budget nightmare?
After 11 years of E5 being the king of the mountain, Microsoft has officially announced its successor: Microsoft 365 E7. Launching May 1, 2026, this isn’t just a minor updateโitโs a $99/month powerhouse designed for an era where AI agents are treated like actual employees.
1. Whatโs inside the E7 Box?
If youโve been “nickel and dimed” by add-on licenses for the last two years, E7 is Microsoftโs way of saying “Fine, hereโs everything.”
- Microsoft 365 Copilot (Wave 3): No more $30 add-on. Itโs baked in, including the new “Coworker” mode developed with Anthropic.
- Agent 365: This is the big one. A brand-new control plane to manage, secure, and govern AI agents across your tenant.
- Microsoft Entra Suite: You get the full identity stack, including Private Access (ZTNA) and Internet Access (SSE), which were previously separate costs.
- Advanced Security: Enhanced features for Defender, Intune, and Purview specifically tuned for “Agentic AI” (AI that actually performs tasks, not just answers questions).
2. The $99 Math: Is it worth it?
At first glance, $99 per user per month sounds like a typo. But letโs look at the “Lazy Admin” math:
| Component | Standalone Cost (Est.) |
| M365 E5 | $60 (post-July 2026 hike) |
| M365 Copilot | $30 |
| Agent 365 | $15 |
| Entra Suite Add-on | $12 |
| Total Value | $117/month |
By moving to E7, youโre saving about $18 per user and, more importantly, you stop managing four different license renewals. That is the definition of working smarter.
3. The “Agentic” Shift
Why do we need E7? Because in 2026, agents are becoming “Frontier Workers.” Microsoftโs new stance is that AI agents need their own identities. Under E7, your automated agents get their own Entra ID, mailbox, and Teams access so they can attend meetings and file reports just like a human. E7 provides the governance layer to make sure these agents don’t go rogue and start emailing your CEO the companyโs secrets.
๐ Microsoft 365 License Comparison: E3 vs. E5 vs. E7
| Feature Category | M365 E3 | M365 E5 | M365 E7 (Frontier) |
| Monthly Cost | ~$36.00 | ~$57.00 | $99.00 |
| Core Productivity | Full Apps + Teams | Full Apps + Teams | Full Apps + Teams |
| Security | Basic (Entra ID P1) | Advanced (Entra ID P2) | Autonomous (P3) |
| Compliance | Core eDiscovery | Inner Risk + Priva | Agentic Governance |
| AI Integration | Add-on Required | Add-on Required | Native Copilot Wave 3 |
| Specialized Tooling | None | Power BI Pro | Agent 365 (Suite) |
| Threat Protection | Defender for Endpoint | Defender XDR Full | Quantum Defender |
| Endpoint Mgmt | Intune (Basic) | Intune (Plan 2) | Autopilot Frontie |
๐ก๏ธ Lazy Admin Verdict:
- Upgrade to E7 if: You already have 50%+ Copilot adoption and youโre starting to build custom AI agents in Copilot Studio.
- Stay on E5 if: Youโre still fighting with users to turn on MFA and haven’t touched AI yet.
๐ References & Further Reading
- Official Microsoft Announcement: Introducing the First Frontier Suite built on Intelligence + Trust โ The primary source for E7 pricing and the “Wave 3” Copilot vision.
- Technical Deep Dive: Secure Agentic AI for your Frontier Transformation โ Details on how Agent 365 integrates with Defender and Purview.
- Partner Insights: Leading Frontier Firm Transformation with Microsoft 365 E7 โ Great for understanding the licensing shift from an MSP/Partner perspective.
- Analysis: M365 E7 to Launch May 1 for $99 Per User Per Month โ Independent analysis of the “Super SKU” value proposition.
Guide to the Entra ID Passkey Rollout (March 2026) | Lazy Admin Blog

How to avoid 500 helpdesk tickets by spending 10 minutes in the Admin Center today.
If you woke up this morning to find a new “Default Passkey Profile” in your Entra tenant, don’t panic. Microsoft is officially “encouraging” (read: forcing) the world toward phishing-resistant auth. As a Lazy Admin, your goal isn’t to fight the changeโit’s to control it so it doesn’t control your weekend.
1. The Big Change: Passkey Profiles
Previously, FIDO2 was a simple “On/Off” switch. Now, we have Passkey Profiles.
- The Default Behavior: If you didn’t opt-in, Microsoft has created a “Default Profile” for you.
- The Trap: If you had “Enforce Attestation” set to No, Microsoft is now allowing Synced Passkeys (iCloud Keychain, Google Password Manager). This means users can put corporate credentials on their personal iPhones.
2. The “Lazy” Strategy: Tiered Security
Don’t treat your CEO and your Summer Intern the same way. Use the new Group-Based Profiles to save yourself the headache of “One Size Fits None.”
| User Group | Recommended Profile | Why? |
| IT Admins | Device-Bound Only | Requires a physical YubiKey or Windows Hello. No syncing to the cloud. |
| Standard Users | Synced & Device-Bound | Maximum convenience. If they lose their phone, iCloud/Google restores the key. Zero helpdesk calls. |
| Contractors | AAGUID Restricted | Only allow specific hardware models youโve issued to them. |
3. Avoid the “Registration Deadlock”
Many admins are seeing “Helpdesk Hell” because their Conditional Access (CA) policies are too strict.
The Problem: You have a policy requiring “Phishing-Resistant MFA” to access “All Apps.” A user tries to register a passkey, but they can’t log in to the registration page because… they don’t have a passkey yet.
The Lazy Fix: Exclude the “Register security information” user action from your strictest CA policies, or better yet, issue a Temporary Access Pass (TAP) for 24 hours. A TAP satisfies the MFA requirement and lets them onboard themselves without calling you.
๐ ๏ธ The 5-Minute “Lazy Admin” Checklist
- [ ] Check your Attestation: Go to Security > Authentication Methods > Passkey (FIDO2). If you want to block personal iPhones, set Enforce Attestation to Yes.
- [ ] Kill the Nudges: If you aren’t ready for the rollout, disable the “Microsoft-managed” registration campaigns before they start bugging your users on Monday.
- [ ] Review AAGUIDs: If you only use YubiKeys, make sure their AAGUIDs are explicitly whitelisted in your Admin profile.
Bottom Line: Spend 10 minutes setting up your profiles today, or spend 10 hours resetting MFA sessions next week. Choose wisely.
Monitoring Disk Command Aborts on ESXi: Identifying Storage Overload | Lazy Admin Blog

When your storage subsystem is severely overloaded, it cannot process commands within the acceptable timeframe defined by the Guest Operating System. The result? Disk Command Aborts. For Windows VMs, this usually triggers after 60 seconds of silence from the storage array.
Aborted commands are a critical red flag indicating that your storage hardware is overwhelmed and unable to meet the host’s performance expectations. Monitoring this parameter is essential for proactive datacenter management.
Here is how you can track these aborts using two primary methods: the vSphere Client and esxtop.
๐ป Method 1: vSphere Client (Graphical Interface)
This method provides a visual, historical look at command aborts across your infrastructure.
- Navigate to Hosts and Clusters.
- Select the object you want to monitor (Host or Cluster).
- Click on the Monitor tab, then Performance, and select Advanced.
- Click Chart Options.
- Switch the metric grouping to Disk.
- Select Commands aborted from the list of measurements.
- Click OK.
๐ ๏ธ Method 2: esxtop (Command Line Interface)
For real-time, granular troubleshooting, esxtop is the definitive tool. It monitors the ABRTS/s (Aborts per Second) field, specifically tracking SCSI aborts.
Steps to Configure esxtop for Aborts:
- Open Putty and log in to your ESXi host via SSH.
- Type
esxtopand press Enter. - Type
uto switch to the Disk Device view. - Type
fto change the field settings. - Type
Lto select Error stats. - Press
Enter, then pressWto save these settings for future sessions.
You will now see the ABRTS/s column. This number represents the SCSI commands aborted by the guest VM during the 1-second collection interval.
๐ Thresholds and Interpretation
If you are deploying a monitoring tool, the critical threshold for ABRTS/s is 1. A value of 1 or higher means SCSI commands are actively being aborted by the guest OS because the storage is not responding.
What is Ideal?
In an ideal scenario, ABRTS/s should always be 0.
What is Real-World?
In a busy production environment, you may see this value fluctuate between 0 and 0.xx. This occurs during “peak hours”โfor instance, when multiple servers on the host are running disk-intensive backup operations simultaneously, leading to temporary storage saturation. However, any consistent spike above 1 requires immediate investigation into path failures, array congestion, or complete storage unresponsiveness.
How to install and configure Multipathing I/O on a computer running Windows Server 2008
To install Multipath I/O
- Open Server Manager.To open Server Manager, click Start, point to Administrative Tools, and then click Server Manager.
- In the Features area, click Add Features.
- On the Select Features page of the Add Features Wizard, select Multipath I/O, and then click Next.
- On the Confirm Installation Selections page, click Install.
- When installation has completed, click Close.
To install Multipath I/O on a computer by using the Server Manager command line, complete the following steps.
To install Multipath I/O by using a command line
- Open a Command Prompt window with elevated privileges.Right-click the Command Prompt object on the Start menu, and then click Run as administrator.
- Type the following, and press ENTER.ย ServerManagerCmd.exe -install Multipath-IO
- When installation has completed, you can verify that Multipath I/O has installed by entering the following command and reviewing the query results in the command window. Multipath I/O should show in the list of installed packages.ย ServerManagerCmd.exe -query
Removing Multipath I/O
To remove Multipath I/O, complete the following steps.
To remove Multipath I/O
- Open Server Manager.To open Server Manager, click Start, point to Administrative Tools, and then click Server Manager.
- In the Features area, click Remove Features.
- On the Select Features page of the Add Features Wizard, select Multipath I/O, and then click Next.
- On the Confirm Installation Selections page, click Install.
- When installation has completed, click Close.
To remove Multipath I/O by using the Server Manager command line, complete the following steps.
To remove Multipath I/O by using a command line
- Open a Command Prompt window with elevated privileges.Right-click the Command Prompt object on the Start menu, and then click Run as administrator.
- Type the following, and press ENTER.ย ServerManagerCmd.exe -remove Multipath-IO
- When removal has completed, you can verify that Multipath I/O was removed by entering the following command and reviewing the query results in the command window. Multipath I/O should not be in the list of installed packages.ย ServerManagerCmd.exe -query
- Select the Add support for iSCSI devices check box, and then click Add. When prompted to restart the computer, click Yes.
- When the computer restarts, the MPIO Devices tab lists the additional hardware ID โMSFT2005iSCSIBusType_0x9.โ When this hardware ID is listed, all iSCSI bus attached devices will be claimed by the Microsoft DSM.
How to fix UCSM login problems with the Java 7 Update 45
This thread was brought to my attention – https://supportforums.cisco.com/thread/2246189
After updating Java to Update 45 – you can no longer login to UCSM (UCS Manager)
You may see one of two errors:
Login Error: java.io.IOException: Invalid Http response
Login Error: java.io.IOException: Server returned HTTP response code: 400 for URL: http://x.x.x.x:443/nuova
Cisco Bug ID: CSCuj84421
This is due to a change introduced in Java
The solution posted is to rollback to Update 25. Rolling back to Update 40 also works.
Help and support service not running in Windows 2003
To fix this:
- Open a Command Prompt
- Run the following commands
- %SystemDrive%
- CD %windir%\PCHealth\HelpCtr\Binaries
- start /w helpsvc /svchost netsvcs /regserver /install
The service should install and start automatically.
How to Clean up the WinSxS Directory on Windows Server 2008 R2
Prior to this we need to install Disk Cleanup on Windows 2008. Disk Cleanup is not installed by default on Windows Server 2008 R2. It is instead a component installed with the Desktop Experience feature.
To install Disk Cleanup without reboot the server – How to install Disk Cleanup without reboot the server in Window 2008
Now download the appropriate package and install the on the system.
| Operating system | Update |
|---|---|
| All supported x86-based versions of Windows 7 | |
| All supported x64-based versions of Windows 7 |
ย ย
Download |
| All supported x64-based versions of Windows Server 2008 R2 |
ย ย
Download |
Looking at my Windows 2008 R2 Server with SP1 installed, according to Windows Explorer, the size of my Windows/WinSxS directory is as follows:
The size of the WinSxS directory will vary by server. Some of you will have smaller WinSxS directories, some larger.
Installing the update is just like installing any other update. Just download and double-click on the .msu file:
Now we need to run the disk cleanup wizard. Disk Cleanup option can be found under Startย –> All Programsย –> Accessoriesย –> System Tools: or go to run and type ‘cleanmgr’ to launch it.
On launch, Disk Cleanup prompts for the drive you want to clean up, default drive will be C: drive
After clicking Ok, a scan is performed:
Several options are provided for cleanup, including a new option for Windows Update Cleanup:
If you didnโt launch Disk Cleanup as Administrator, at this point, youโll need to take a couple extra steps. Youโll need to click on the Clean up system files button.
The actual cleanup occurs during the next reboot. After the reboot, taking a look at the WinSxS directory, it has shrunk to the following:
How to change default snapshot location in VMware ESXi 5
Defaulty the snapshots which are taken for any virtual machine are stored with their parent in the same directory or storage. Sometimes you may run out of space and you might not be able to take anymore snapshots so in that case you can always use some other location for the storage of snapshots.

These are the required steps to be taken toย change the default locations of all the snapshots .
NOTE: Please ensure that the vm you are working on is powered OFF.
Right Click the vm and selectย Edit Settings
Click onย Optionsย from the top TAB, selectย Generalย and open theย Configuration parameters
Add a new row with the following details
snapshot.redoNotWithParent
Save this parameter with a value “true” as shown below
Now open the CLI of the host where the vm is located
Go to the vm’s parent directory where all the vm files are stored and open the main .vmx file
As in my case
# cd /vmfs/volumes/53652b45-90f342h4-v3r3-s5dw676h5674/Windows2003
# vi Windows2003.vmx
Now add this line anywhere in the .vmx file with the path location where you want your snapshots to be stored
workingDir = “/vmfs/volumes/54332bf4-gd3bf353-g45b-g2ft353b5545/snapshots”
Save the file and exit
Now you need to reload this vm to make the changes take affect.
# vim-cmd vmsvc/getallvms | grepย Windows2003
13ย Windows2003 [iSCSI-Datastore15] Windows2003/Windows2003 win2003ย vmx-07
Here 13ย is the vm id which you can find out using the above command
# vim-cmd vmsvc/reloadย 13
Now when you take snapshots the snapshot files and vm swap files will be created in a different location.
How to redirect vm’s swap file
In case you do not want vm swap file to be redirected to another location and you want it to the same parent directory.
Add an extra parameter in the Configuration Parameter option shown above
sched.swap.dir=”<path_to_vm_directory>”
For example
/vmfs/volumes/54332bf4-gd3bf353-g45b-g2ft353b5545/vmswap
Save the settings and exit. Now each time you take snapshot the snapshot files and vm swap files will be saved at specified different location.
Installing Disk Cleanup In Windows 2008 Without Rebooting The Server
The Disk Cleanup executable file cleanmgr.exe and the associated Disk Cleanup button are not present in Windows Serverยฎ 2008 or in Windows Serverยฎ 2008 R2 by default.ย This is by design, as the Disk Cleanup button is part of the Desktop Experience feature. In order to have Disk Cleanup button appear on a diskโs Properties dialog, you will need to install the Desktop Experience feature.
So in order to use cleanmgr.exe youโll need to copy two files that are already present on the server, cleanmgr.exe and cleanmgr.exe.mui. Use the following table to locate the files for your operating system.
Windows Server 2008 R2 64 bit
C:\Windows\winsxs\amd64_microsoft-windows-cleanmgr_31bf3856ad364e35_6.1.7600.16385_none_c9392808773cd7da\cleanmgr.exe
Windows Server 2008 R2ย 64-bit
C:\Windows\winsxs\amd64_microsoft-windows-cleanmgr.resources_31bf3856ad364e35_6.1.7600.16385_en-us_b9cb6194b257cc63\cleanmgr.exe.mui
Windows Server 2008 64-bit
C:\Windows\winsxs\amd64_microsoft-windows-cleanmgr.resources_31bf3856ad364e35_6.0.6001.18000_en-us_b9f50b71510436f2\cleanmgr.exe.mui
Windows Server 2008 64-bit
C:\Windows\winsxs\amd64_microsoft-windows-cleanmgr_31bf3856ad364e35_6.0.6001.18000_none_c962d1e515e94269\cleanmgr.exe.mui
Windows Server 2008 32-bit
C:\Windows\winsxs\x86_microsoft-windows-cleanmgr.resources_31bf3856ad364e35_6.0.6001.18000_en-us_5dd66fed98a6c5bc\cleanmgr.exe.mui
Windows Server 2008 32-bit
C:\Windows\winsxs\x86_microsoft-windows-cleanmgr_31bf3856ad364e35_6.0.6001.18000_none_6d4436615d8bd133\cleanmgr.exe
Once youโve located the files move them to the following locations:
1. Cleanmgr.exe should go in %systemroot%\System32
2. Cleanmgr.exe.mui should go in %systemroot%\System32\en-US
You can now launch the Disk cleanup tool by running Cleanmgr.exe from the command promptย or by clicking Start and typing Cleanmgr into the Search bar.
Restart the domain controller in Directory Services Restore Mode locally
If you have physical access to a domain controller, you can restart the domain controller in Directory Services Restore Mode locally. Restarting in Directory Services Restore Mode takes the domain controller offline. In this mode, the server is not functioning as a domain controller.
When you start Windowsย Serverย 2003 in Directory Services Restore Mode, the local Administrator account is authenticated by the local Security Accounts Manager (SAM) database. Therefore, logging on requires that you use the local administrator password, not an Activeย Directory domain password. This password is set during Activeย Directory installation when you provide the password for Directory Services Restore Mode.
Administrative credentials
To perform this procedure, you must provide the Administrator password for Directory Services Restore Mode.
To restart the domain controller in Directory Services Restore Mode locally
- Restart the domain controller.
- When the screen for selecting an operating system appears, press F8.
- On the Windows Advanced Options menu, select Directory Services Restore Mode.
- When you are prompted, log on as the local administrator.
Change the static IP address of a domain controller
Administrative Credentials
To perform this procedure, you must be a member of the Domain Admins group in the domain of the domain controller whose IP address you are changing.
To change the static IP address of a domain controller
- Log on locally (also known as interactively) to the system console of the domain controller whose IP address you want to change. If you are not able to log on to the domain controller by using the domain, you may have to start the domain controller in Directory Services Restore Mode (DSRM). For more information, see Restart the domain controller in Directory Services Restore Mode locally (https://lazyadminblog.com/2015/04/11/restart-the-domain-controller-in-directory-services-restore-mode-locally/).
On the desktop, right-clickย My Network Places, and then clickย Properties.
- In theNetwork Connectionsย dialog box, right-clickย Local Area Connection, and then clickย Properties.
- In theLocal Area Connection Propertiesย dialog box, double-clickย Internet Protocol (TCP/IP).
- In theInternet Protocol (TCP/IP) Propertiesย dialog box, in theย IP addressย box, type the new address.
- In theSubnet maskย box, type the subnet mask.
- In theDefault gatewayย box, type the default gateway.
- In thePreferred DNS serverย box, type the address of the DNS server that this computer contacts.
- In theAlternate DNS serverย box, type the address of the DNS server that this computer contacts if the preferred server is unavailable.
- If this domain controller uses WINS servers, clickAdvancedย and then, in theย Advanced TCP/IP Settingsย dialog box, click theย WINS
- If an address in the list is no longer appropriate, click the address, and then clickEdit.
- In theTCP/IP WINS Serverย dialog box, type the new address, and then clickย OK.
- Repeat steps 11 and 12 for all addresses that need to be changed, and then clickOKย twice to close theย TCP/IP WINS Serverย dialog box and theย Advanced TCP/IP Settingsย dialog box.
- ClickOKย to close theย Internet Protocol (TCP/IP) Propertiesย dialog box.
After you change the IP address of a domain controller, you should run theย ipconfig /registerdnsย command to register the host record andย dcdiag /fixย command to ensure that service records are appropriately registered with DNS. For more information, see Dcdiag Overview and subordinate topics for additional information about the Dcdiag tool (https://lazyadminblog.com/2015/04/11/dcdiag-overview/).
Changing the IP settings of a server does not affect the share resources or shared permissions on that server, if the name resolution structure DNS and WINS settings are correctly configured. However, if network drives or passive connections (connections that are made manually from a command prompt or run line) are mapped using the IP address, an update is required. For example, if a client computer has G: drive mapped using the following commandย net use g: \\192.168.0.199\dataย and the IP address of the server that hosts the Data shared folder is changed from 192.168.0.199 to 192.168.1.200, the new G: drive mapping command should be changed toย net use g: \\192.168.1.200\data. A better solution would be to ensure that DNS name resolution is working properly and to use the server name, as opposed to the IP address, in the command. For example, if the server name is DC1, the command to map a G: drive to the Data share on the server isย net use g: \\dc1\data. It changes only if the server name changes; it is not affected if the IP address of the server changes.
Using esxtop to identify storage performance issues for ESX / ESXi (multiple versions) (1008205)
The interactive esxtop utility can be used to provide I/O metrics over various devices attached to a VMware ESX host.
Configuring monitoring using esxtop
ย To monitor storage performance per HBA:
- Start esxtop by typing esxtop at the command line.
- Press d to switch to disk view (HBA mode).
- To view the entire Device name, press SHIFT + L and enter 36 in Change the name field size.
- Press f to modify the fields that are displayed.
- Press b, c, d, e, h, and j to toggle the fields and press Enter.
- Pressย s and thenย 2ย to alter the update time to every 2 seconds and press Enter.
- See Analyzing esxtop columns for a description of relevant columns. For more information, see Interpreting esxtop Statistics.
Note: These options are available only in VMware ESX 3.5 and later.
To monitor storage performance on a per-LUN basis:
- Start esxtop by typing esxtop from the command line.
- Press u to switch to disk view (LUN mode).
- Press f to modify the fields that are displayed.
- Pressย b, c, f, and h to toggle the fields and press Enter.
- Press s and then 2 to alter the update time to every 2 seconds and press Enter.
- See Analyzing esxtop columns for a description of relevant columns. For more information, see Interpreting esxtop Statistics.
To increase the width of the device field in esxtop to show the complete naa id:
- Start esxtop by typing esxtop at the command line.
- Press u to switch to the disk device display.
- Press Lย to change the name field size.Note: Ensure to use uppercase L.
- Enter the value 36 to display the complete naa identifier.
To monitor storage performance on a per-virtual machine basis:
- Start esxtop by typing esxtop at the command line.
- Type v to switch to disk view (virtual machine mode).
- Press f to modify the fields that are displayed.
- Pressย b, d, e, h, and j to toggle the fields and press Enter.
- Press s and then 2 to alter the update time to every 2 seconds and press Enter.
- See Analyzing esxtop columns for a description of relevant columns. For more information, see Interpreting esxtop Statistics.
Analyzing esxtop columns
Refer to this table for relevant columns and descriptions of these values:
| Column | ย Description |
| CMDS/s | This is the total amount of commands per second and includes IOPS (Input/Output Operations Per Second) and other SCSI commands such as SCSI reservations, locks, vendor string requests, unit attention commands etc.ย being sent to or coming from the device or virtual machine being monitored.In most cases, CMDS/s = IOPS unless there are a lot of metadata operations (such as SCSI reservations) |
| DAVG/cmd | This is the average response time in milliseconds per command being sent to the device. |
| KAVG/cmd | This is the amount of time the command spends in the VMkernel. |
| GAVG/cmd | This is the response time as it is perceived by the guest operating system. This number is calculated with the formula: DAVG + KAVG = GAVG |
These columns are for both reads and writes, whereas xAVG/rd is for reads and xAVG/wr is for writes. The combined value of these columns is the best way to monitor performance, but high read or write response time it may indicate that the read or write cache is disabled on the array. All arrays perform differently, however,ย DAVG/cmd, KAVG/cmd, and GAVG/cmd should not exceed more than 10 milliseconds (ms) for sustained periods of time.
Note: VMware ESX 3.0.x does not include direct functionality to monitor individual LUNs or virtual machines using esxtop. Inactive LUNs lower the average for DAVG/cmd, KAVG/cmd, and GAVG/cmd. These values are also visible from the vCenter Server performance charts. For more information, see theย Performance Charts section in the Basic System Administration Guide.
If you experience high latency times, investigate current performance metrics and running configuration for the switches and the SAN targets. Check for errors or logging that may suggest a delay in operations being sent to, received, and acknowledged. This includes the array’s ability to process I/O from a spindle count aspect, or the array’s ability to handle the load presented to it.
If the response time increases to over 5000 ms (or 5 seconds), VMware ESX will time out the command and abort the operation. These events are logged; abort messages and other SCSI errors can be reviewed in these logs:
- ESX 3.5 and 4.x โ /var/log/vmkernel
- ESXi 3.5 and 4.x โ /var/log/messagesย
- ESXi 5.x and later – /var/log/vmkernel.log
The type of storage logging you may see in these files depends on the configuration of the server. You can find the value of these optionsย by navigating toย Host > Configuration > Advanced Settings > SCSI > SCSI.Log* or SCSI.Print*.
Connecting to a virtual machine console fails with the error: The VMRC Console has Disconnected. Trying to reconnect (2050470)
Error: The VMRC Console has Disconnected.. Trying to reconnect
If this happens, then the VM will not be reachble on the network and you cannot see black screen on the VM console.
To fix this, killย the vmware-vmrc.exe*32 service from Windows Task Manager and thenย open the console again.
Automation: Bulk Create and Delete VM Snapshots Across Linked vCenters | Lazy Admin Blog

In a large environment, taking snapshots before a major patch or application update is a standard safety net. But if you have servers spread across multiple vCenters in Linked Mode (e.g., Datacenter1 and Datacenter2), clicking through the vSphere Client is a waste of time.
Today, I’m sharing a “Lazy Admin” script that allows you to bulk create, check, and remove snapshots using a simple CSV list.
Prerequisites
- VMware PowerCLI: Ensure the PowerCLI module is installed on the machine running the script.
- CSV Setup: Create a file named
snapshot_servers.csvinC:\Temp\VMSnapshots\.
The CSV should look like this: | Host | Location | | :— | :— | | Server01 | Datacenter1 | | Server02 | Datacenter2 |
Part 1: Creating Snapshots
- Open PowerShell ISE with vCenter Administrator credentials.
- Load the functions by running the full script (provided below).
- Run the following command:
Create-VMSnapshots -SS_CSV "C:\Temp\VMSnapshots\snapshot_servers.csv" -SS_Name "Pre-Patching" -SS_Description "Requested by App Team"
The script will iterate through your CSV and create snapshots sequentially. You can monitor the progress in the vSphere Tasks console.
Part 2: Deleting Snapshots
Once your changes are verified, don’t let those snapshots linger and bloat your datastores! To remove them:
- Use the same
snapshot_servers.csvlist. - Run the following command:
Remove-VMSnapshots -SS_CSV "C:\Temp\VMSnapshots\snapshot_servers.csv"
Note: This is a sequential script; it will wait for one snapshot removal to finish before moving to the next to avoid pinning your storage I/O.
The Script: VMSnapshots.ps1
Save this code to C:\Temp\VMSnapshots\VMSnapshots.ps1.
function Create-VMSnapshots { param ( [string]$SS_CSV = $(Read-Host "Enter path to CSV"), [string]$SS_Name = $(Read-Host "Enter name for snapshots"), [string]$SS_Description = $(Read-Host "Enter description for snapshots") ) # Import VMware PowerCLI Module If ( !(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) ) { import-module VMware.VimAutomation.Core } $Servers = Import-CSV $SS_CSV $WLM_vCenter = Connect-VIServer vCenter1 -WarningAction SilentlyContinue $EDN_vCenter = Connect-VIServer vCenter2 -WarningAction SilentlyContinue ForEach($Server in $Servers){ If($Server.Location -eq 'Datacenter1'){ New-Snapshot -VM $Server.Host -Name $SS_Name -Description $SS_Description -Quiesce -Server $WLM_vCenter -WarningAction SilentlyContinue } ElseIf($Server.Location -eq 'Datacenter2'){ New-Snapshot -VM $Server.Host -Name $SS_Name -Description $SS_Description -Quiesce -Server $EDN_vCenter -WarningAction SilentlyContinue } } }function Check-VMSnapshots { param ( [string]$SS_CSV = $(Read-Host "Enter path to CSV"), [string]$SS_Name = $(Read-Host "Enter snapshot name") ) # Import VMware PowerCLI Module If ( !(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) ) { import-module VMware.VimAutomation.Core } $Servers = Import-CSV $SS_CSV $WLM_vCenter = Connect-VIServer vCenter1 -WarningAction SilentlyContinue $EDN_vCenter = Connect-VIServer vCenter2 -WarningAction SilentlyContinue ForEach($Server in $Servers){ If($Server.Location -eq 'Datacenter1'){ Get-Snapshot -VM $Server.Host -Name $SS_Name -Server $WLM_vCenter | Select VM, Name, @{ n="SpaceUsedGB"; e={[math]::round( $_.SizeGB )}} -WarningAction SilentlyContinue } ElseIf($Server.Location -eq 'Datacenter2'){ Get-Snapshot -VM $Server.Host -Name $SS_Name -Server $EDN_vCenter | Select VM, Name, @{ n="SpaceUsedGB"; e={[math]::round( $_.SizeGB )}} -WarningAction SilentlyContinue } } } function Remove-VMSnapshots { param ( [string]$SS_CSV = $(Read-Host "Enter path to CSV") ) # Import VMware PowerCLI Module If ( !(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) ) { import-module VMware.VimAutomation.Core } $Servers = Import-CSV $SS_CSV $WLM_vCenter = Connect-VIServer vCenter1 -WarningAction SilentlyContinue $EDN_vCenter = Connect-VIServer vCenter2 -WarningAction SilentlyContinue ForEach($Server in $Servers){ If($Server.Location -eq 'Datacenter1'){ Get-Snapshot $Server.Host -Server $WLM_vCenter | Remove-Snapshot -Confirm:$false -WarningAction SilentlyContinue } ElseIf($Server.Location -eq 'Datacenter2'){ Get-Snapshot $Server.Host -Server $EDN_vCenter | Remove-Snapshot -Confirm:$false -WarningAction SilentlyContinue } } }
Build Your Own VM Snapshot GUI with PowerShell | Lazy Admin Blog

The ultimate “I’m too busy for CLI” tool for VMware Admins.
Today, I am sharing a complete PowerShell tool that creates a custom Windows Form to manage bulk snapshots across multiple vCenters, complete with a progress bar and an automated HTML email report.
๐ What this tool does:
- Multi-vCenter Support: Toggle between environments with simple radio buttons.
- Bulk Processing: Paste a list of hostnames directly into the text box.
- Smart Memory Handling: Choose whether to include VM memory in the snapshot.
- Progress Tracking: A real-time progress bar so you know exactly when it’s safe to go grab another coffee.
- Automated Reporting: Generates a CSV on your desktop and sends a formatted HTML email to your team.
The Script: VM-Snapshot-GUI.ps1
Lazy Admin Note: Before running, make sure you have the VMware PowerCLI module installed. Change the
"SMTP Server"and"From@domain.com"strings in the script to match your environment.
<!-- wp:syntaxhighlighter/code -->
<pre class="wp-block-syntaxhighlighter-code">Function Snapshot()
{
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Windows.Forms
# Create the Main form.
$form = New-Object System.Windows.Forms.Form
$form.Text = "VM Snapshot"
$form.Size = New-Object System.Drawing.Size(650,320)
$form.FormBorderStyle = 'FixedSingle'
$form.StartPosition = "CenterScreen"
$form.AutoSizeMode = 'GrowAndShrink'
$form.Topmost = $True
$form.ShowInTaskbar = $true
# Create the Email form.
$Emailform = New-Object System.Windows.Forms.Form
$Emailform.Text = "Email Report"
$Emailform.Size = New-Object System.Drawing.Size(420,200)
$Emailform.FormBorderStyle = 'FixedSingle'
$form.StartPosition = "CenterScreen"
$Emailform.AutoSizeMode = 'GrowAndShrink'
$Emailform.Topmost = $True
$Emailform.ShowInTaskbar = $true
#Select vCenter
$groupBox = New-Object System.Windows.Forms.GroupBox
$groupBox.Location = New-Object System.Drawing.Size(10,20)
$groupBox.size = New-Object System.Drawing.Size(180,80)
$groupBox.text = "Select the vCenter:"
$Form.Controls.Add($groupBox)
$RadioButton1 = New-Object System.Windows.Forms.RadioButton
$RadioButton1.Location = new-object System.Drawing.Point(15,15)
$RadioButton1.size = New-Object System.Drawing.Size(160,25)
$RadioButton1.Checked = $true
$RadioButton1.Text = "vCenter 1"
$groupBox.Controls.Add($RadioButton1)
$RadioButton2 = New-Object System.Windows.Forms.RadioButton
$RadioButton2.Location = new-object System.Drawing.Point(15,40)
$RadioButton2.size = New-Object System.Drawing.Size(160,25)
$RadioButton2.Text = "vCenter 2"
$groupBox.Controls.Add($RadioButton2)
#Select Snapshot memory
$groupBox1 = New-Object System.Windows.Forms.GroupBox
$groupBox1.Location = New-Object System.Drawing.Size(200,20)
$groupBox1.size = New-Object System.Drawing.Size(180,80)
$groupBox1.text = "Snapshot Memory:"
$Form.Controls.Add($groupBox1)
$RadioButton3 = New-Object System.Windows.Forms.RadioButton
$RadioButton3.Location = new-object System.Drawing.Point(15,15)
$RadioButton3.size = New-Object System.Drawing.Size(160,25)
$RadioButton3.Checked = $true
$RadioButton3.Text = "Snapshot Without Memory"
$groupBox1.Controls.Add($RadioButton3)
$RadioButton4 = New-Object System.Windows.Forms.RadioButton
$RadioButton4.Location = new-object System.Drawing.Point(15,40)
$RadioButton4.size = New-Object System.Drawing.Size(160,25)
$RadioButton4.Text = "Snapshot With Memory"
$groupBox1.Controls.Add($RadioButton4)
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Size(390,20)
$label.Size = New-Object System.Drawing.Size(280,20)
$label.AutoSize = $true
$label.Text = "Enter Hostname Here..."
# Create the TextBox used to capture the user's text.
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Size(390,40)
$textBox.Size = New-Object System.Drawing.Size(130,230)
$textBox.AcceptsReturn = $true
$textBox.AcceptsTab = $false
$textBox.Multiline = $true
$textBox.ScrollBars = 'Both'
$textbox.CharacterCasing='Upper'
# Getting Input for Snapshot SR#, Snapshot Name, Snapshot Description.
$SR_label = New-Object System.Windows.Forms.Label
$SR_label.Location = New-Object System.Drawing.Size(15,130)
$SR_label.Size = New-Object System.Drawing.Size(280,20)
$SR_label.AutoSize = $true
$SR_label.Text = "*SR#"
$SR_textBox = New-Object System.Windows.Forms.TextBox
$SR_textBox.Location = New-Object System.Drawing.Size(150,130)
$SR_textBox.Size = New-Object System.Drawing.Size(130,20)
$SR_textBox.AcceptsReturn = $true
$SR_textBox.AcceptsTab = $false
$SR_textbox.CharacterCasing='Upper'
$SN_label = New-Object System.Windows.Forms.Label
$SN_label.Location = New-Object System.Drawing.Size(15,160)
$SN_label.Size = New-Object System.Drawing.Size(280,20)
$SN_label.AutoSize = $true
$SN_label.Text = "*Snapshot Name"
$SN_textBox = New-Object System.Windows.Forms.TextBox
$SN_textBox.Location = New-Object System.Drawing.Size(150,160)
$SN_textBox.Size = New-Object System.Drawing.Size(200,20)
$SN_textBox.AcceptsReturn = $true
$SN_textBox.AcceptsTab = $false
#$SN_textbox.CharacterCasing='Upper'
$SD_label = New-Object System.Windows.Forms.Label
$SD_label.Location = New-Object System.Drawing.Size(15,190)
$SD_label.Size = New-Object System.Drawing.Size(280,20)
$SD_label.AutoSize = $true
$SD_label.Text = "Snapshot Description"
$SD_textBox = New-Object System.Windows.Forms.TextBox
$SD_textBox.Location = New-Object System.Drawing.Size(150,190)
$SD_textBox.Size = New-Object System.Drawing.Size(200,50)
$SD_textBox.AcceptsReturn = $true
$SD_textBox.AcceptsTab = $false
$SD_textBox.Multiline = $true
$SD_textBox.ScrollBars = 'Both'
#$SD_textbox.CharacterCasing='Upper'
#Create the Hardening Button.
$HButton = New-Object System.Windows.Forms.Button
$HButton.Location = New-Object System.Drawing.Size(540,40)
$HButton.Size = New-Object System.Drawing.Size(100,40)
$HButton.Text = "Take Snapshot"
#Create the Report Button.
$RButton = New-Object System.Windows.Forms.Button
$RButton.Location = New-Object System.Drawing.Size(540,90)
$RButton.Size = New-Object System.Drawing.Size(100,40)
$RButton.Text = "Generate Report"
#Create the Report Button.
$EmailButton = New-Object System.Windows.Forms.Button
$EmailButton.Location = New-Object System.Drawing.Size(540,140)
$EmailButton.Size = New-Object System.Drawing.Size(100,40)
$EmailButton.Text = "Send Email"
#Create the Progress-Bar.
$label1 = New-Object System.Windows.Forms.Label
$label1.Location = New-Object System.Drawing.Size(20,230)
$label1.Size = New-Object System.Drawing.Size(280,20)
$label1.AutoSize = $true
$label1.Text = "Progress..."
$PB = New-Object System.Windows.Forms.ProgressBar
$PB.Name = "PowerShellProgressBar"
$PB.Value = 0
$PB.Style="Continuous"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 200 - 40
$System_Drawing_Size.Height = 20
$PB.Size = $System_Drawing_Size
$PB.Left = 20
$PB.Top = 250
#Initiate Snapshot
$HButton.Add_Click(
{
$report= @()
$counter = 0
If ($SR_textBox.TextLength -ne 0 -and $SN_textBox.TextLength -ne 0)
{
$S_Name= $SR_textBox.text+ " - " +$SN_textBox.text
}
Else{
[System.Windows.Forms.MessageBox]::Show("SR# or Snapshot Name cannot be blank", "Info")
return
}
If ($textbox.TextLength -eq 0)
{
[System.Windows.Forms.MessageBox]::Show("Server List is empty", "Info")
return
}
[System.Windows.Forms.MessageBox]::Show("Sit back and relax while Snapshot is taken !!!", "VM Snapshot")
$ServerList=$textbox.Text.Split("`n")|%{$_.trim()}
Foreach ($vm in $ServerList)
{
if($vm -eq "")
{
$counter++
[Int]$Percentage = ($Counter/$ServerList.Count)*100
$PB.Value = $Percentage
continue
}
if ($RadioButton1.Checked -eq $True)
{
Add-PSSnapin VMware.VimAutomation.Core
Connect-VIServer -Server vCenter1
}
if ($RadioButton2.Checked -eq $True)
{
Add-PSSnapin VMware.VimAutomation.Core
Connect-VIServer -Server vCenter2
}
$Exists = get-vm -name $vm -ErrorAction SilentlyContinue
If ($Exists)
{
If ($RadioButton3.Checked -eq $True)
{
Get-VM $vm |New-snapshot -Name $S_Name -Description $SD_textBox.Text
$rep = Get-VM $vm | Get-Snapshot | Select-Object @{Name='VM';Expression={$_.vm}},@{Name='Snapshot_Name';Expression={$_.name}},@{Name='Description';Expression={$_.Description}},@{Name='Created';Expression={$_.Created}},@{Name='Remarks';Expression={""}}
$report = $report + $rep
}
If ($RadioButton4.Checked -eq $True)
{
Get-VM $vm |New-snapshot -Name $S_Name -Description $SD_textBox.Text -Memory
$rep = Get-VM $vm | Get-Snapshot | Select-Object @{Name='VM';Expression={$_.vm}},@{Name='Snapshot_Name';Expression={$_.name}},@{Name='Description';Expression={$_.Description}},@{Name='Created';Expression={$_.Created}},@{Name='Remarks';Expression={""}}
$report = $report + $rep
}
}
If (!$Exists)
{
$row= New-Object PSObject -Property @{VM = $vm;Snapshot_Name = "";Description = "";Created = "";Remarks="Server not Found"}
$report += $row
}
$counter++
[Int]$Percentage = ($Counter/$ServerList.Count)*100
$PB.Value = $Percentage
}
[System.Windows.Forms.MessageBox]::Show("Snapshot taken successfully" , "Report Generation")
$report |Select-object @{Name="HOSTNAME"; Expression={$_.VM}},@{Name="Snapshot_Name"; Expression={$_.Snapshot_Name}},@{Name="Description"; Expression={$_.Description}},@{Name="Created: Date & Time"; Expression={$_.Created}},@{Name='Remarks';Expression={$_.Remarks}}| Export-Csv $file -NoTypeInformation
})
#Report Generation
$RButton.Add_Click(
{
ii $path
})
#Send Email
$EmailButton.Add_Click(
{
#Create Label
$ToLabel = New-Object System.Windows.Forms.Label
$ToLabel.Location = New-Object System.Drawing.Size(20,20)
$ToLabel.Size = New-Object System.Drawing.Size(100,20)
$ToLabel.AutoSize = $true
$ToLabelFont = New-Object Drawing.Font("Times New Roman",12,[System.Drawing.FontStyle]::Bold)
$ToLabel.Font = $ToLabelFont
$ToLabel.Text = "*To:"
$CcLabel = New-Object System.Windows.Forms.Label
$CcLabel.Location = New-Object System.Drawing.Size(20,50)
$CcLabel.Size = New-Object System.Drawing.Size(100,20)
$CcLabel.AutoSize = $true
$CcLabelFont = New-Object Drawing.Font("Times New Roman",12,[System.Drawing.FontStyle]::Bold)
$CcLabel.Font = $CcLabelFont
$CcLabel.Text = "Cc: (Optional)"
#Create the TextBox Email Address.
$ToBox = New-Object System.Windows.Forms.TextBox
$ToBox.Location = New-Object System.Drawing.Size(140,20)
$ToBox.Size = New-Object System.Drawing.Size(250,20)
$ToBoxFont = New-Object Drawing.Font("Times New Roman",8)
$ToBox.Font=$ToBoxFont
$ToBox.AcceptsReturn = $true
$ToBox.AcceptsTab = $false
#$ToBox.text=""
$ToBox.CharacterCasing='lower'
$CcBox = New-Object System.Windows.Forms.TextBox
$CcBox.Location = New-Object System.Drawing.Size(140,50)
$CcBox.Size = New-Object System.Drawing.Size(250,20)
$CcBoxFont = New-Object Drawing.Font("Times New Roman",8)
$CcBox.Font=$CcBoxFont
$CcBox.AcceptsReturn = $true
$CcBox.AcceptsTab = $false
$CcBox.text=""
$CcBox.CharacterCasing='lower'
#Create Email Send Button
$SendButton = New-Object System.Windows.Forms.Button
$SendButton.Location = New-Object System.Drawing.Size(20,100)
$SendButton.Size = New-Object System.Drawing.Size(100,40)
$ButtonFont = New-Object Drawing.Font("Times New Roman",8,[System.Drawing.FontStyle]::Bold)
$SendButton.Font=$ButtonFont
$SendButton.Text = "Send Email"
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150,100)
$CancelButton.Size = New-Object System.Drawing.Size(100,40)
$CancelButton.Font=$ButtonFont
$CancelButton.Text = "Cancel"
$Emailform.Controls.Add($ToLabel)
$Emailform.Controls.Add($CcLabel)
$Emailform.Controls.Add($ToBox)
$Emailform.Controls.Add($CCBox)
$Emailform.Controls.Add($SendButton)
$Emailform.Controls.Add($CancelButton)
$SendButton.Add_Click(
{
If ($ToBox.Text.Length -eq 0)
{
[System.Windows.Forms.MessageBox]::Show("Email address cannot be empty" , "Email Info")
return
}
#---------------------------------------------------------------------
# Generate the HTML report and output to file
#---------------------------------------------------------------------
$head = "<style>"
$head = $head + "BODY{background-color:white;}"
$head = $head + "TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}"
$head = $head + "TH{border-width: 1px;padding: 0px;border-style: solid;border-color: black;background-color:#778899}"
$head = $head + "TD{border-width: 1px;padding: 0px;border-style: solid;border-color: black}"
$head = $head + "</style>"
# SMTP info
$Toemail=$ToBox.Text
$strTo=$Toemail
$strCc=$CCBox.Text
$strSubject = "Snapshot Taken : $S_Name"
$StrMsg="Hi All, <br>Snapshot has been taken successfully for below list of servers <br><br>"
$strBody = "Attached is the list of Snapshots"
$strMail = $strmsg
# Write the output to an HTML file
$strOutFile = $Path+"email.html"
$ComputerName=(Get-WmiObject -Class Win32_ComputerSystem -Property Name).Name
Get-Content -Path $File |ConvertFrom-CSV | ConvertTo-HTML -Head $head -Body $StrMsg | Out-File $StrOutFile
# Mail the output file
$msg = new-object Net.Mail.MailMessage
$att = new-object Net.Mail.Attachment($File)
$smtp = new-object Net.Mail.SmtpClient("SMTP Server")
$msg.From ="From@domain.com"
$msg.To.Add($strTo)
If ($strCc.Length -ne 0)
{
$msg.cc.Add($strcc)
}
$msg.Subject = $strSubject
$msg.IsBodyHtml = 1
$msg.Body = Get-Content $strOutFile
$msg.Attachments.Add($att)
$smtp.Send($msg)
[System.Windows.Forms.MessageBox]::Show("Email Send !!!","Info")
$Emailform.close()
})
$CancelButton.Add_Click(
{
$Emailform.close()
})
$Emailform.Add_Shown({$Emailform.Activate()})
$Emailform.ShowDialog() > $null
})
# Add all of the controls to the form.
$form.Controls.Add($label)
$form.Controls.Add($textBox)
$form.Controls.Add($groupBox)
$form.Controls.Add($groupBox1)
$form.Controls.Add($RButton)
$form.Controls.Add($HButton)
$form.Controls.Add($label1)
$form.Controls.Add($SR_label)
$form.Controls.Add($SR_textBox)
$form.Controls.Add($SN_label)
$form.Controls.Add($SN_textBox)
$form.Controls.Add($SD_label)
$form.Controls.Add($SD_textBox)
$form.Controls.Add($PB)
$form.Controls.Add($EmailButton)
# Initialize and show the form.
$form.Add_Shown({$form.Activate()})
$form.ShowDialog() > $null
}
Set-ExecutionPolicy unrestricted -Force
$date=get-Date -format "ddMMyy_HHmm"
$ComputerName=(Get-WmiObject -Class Win32_ComputerSystem -Property Name).Name
$Path=[System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::Desktop)+"\Report\"
If ((Test-Path $Path) -eq $false)
{
New-Item $Path -type directory
}
New-Item -ErrorAction Ignore -ItemType directory -Path Report
$File=$Path + "SnapshotReport_$date.csv"
Snapshot</pre>
<!-- /wp:syntaxhighlighter/code -->
๐ ๏ธ How to use the GUI:
- Select your vCenter: Choose which environment the VMs live in.
- Snapshot Specs: Enter the SR# (Service Request) and a Name. These are mandatory to keep your environment organized.
- Input Servers: Paste your list of servers (one per line) into the right-hand text box.
- Hit ‘Take Snapshot’: The tool will cycle through the list. If a server isn’t found, it won’t crash; it just marks it as “Server not Found” in your final report.
- Send Email: Once finished, click ‘Send Email’ to notify your team that the work is done.
Why this belongs in your toolkit:
The “Lazy Admin” way is about standardization. By using this GUI, every snapshot taken by your team will follow the same naming convention: SR# - Snapshot Name. No more guessing what “Snap1” or “Update_v2” means six months from now.
๐ ๏ธ The Refactor: Adding the “Cleanup” Tab
In the code below, Iโve introduced the TabControl and TabPage classes. The new Cleanup Tab includes a feature every admin needs: Age-Based Filtering. It can scan for snapshots older than 2 or 3 days and wipe them in bulk.
<!-- wp:syntaxhighlighter/code -->
<pre class="wp-block-syntaxhighlighter-code">Function Snapshot()
{
ย ย Add-Type -AssemblyName System.Drawing
ย ย Add-Type -AssemblyName System.Windows.Forms
ย ย ย
ย ย # Create the Main form.
ย ย $form = New-Object System.Windows.Forms.Formย
ย ย $form.Text = "VM Snapshot"
ย ย $form.Size = New-Object System.Drawing.Size(650,320)
ย ย $form.FormBorderStyle = 'FixedSingle'
ย ย $form.StartPosition = "CenterScreen"
ย ย $form.AutoSizeMode = 'GrowAndShrink'
ย ย $form.Topmost = $True
ย ย $form.ShowInTaskbar = $trueย ย
ย ย # Create the Email form.
ย ย $Emailform = New-Object System.Windows.Forms.Formย
ย ย $Emailform.Text = "Email Report"
ย ย $Emailform.Size = New-Object System.Drawing.Size(420,200)
ย ย $Emailform.FormBorderStyle = 'FixedSingle'
ย ย $form.StartPosition = "CenterScreen"
ย ย $Emailform.AutoSizeMode = 'GrowAndShrink'
ย ย $Emailform.Topmost = $True
ย ย $Emailform.ShowInTaskbar = $trueย ย
ย ย ย
ย ย #Select vCenter
ย ย $groupBox = New-Object System.Windows.Forms.GroupBox
ย ย $groupBox.Location = New-Object System.Drawing.Size(10,20)ย
ย ย $groupBox.size = New-Object System.Drawing.Size(180,80)ย
ย ย $groupBox.text = "Select the vCenter:"ย
ย ย $Form.Controls.Add($groupBox)ย
ย ย $RadioButton1 = New-Object System.Windows.Forms.RadioButtonย
ย ย $RadioButton1.Location = new-object System.Drawing.Point(15,15)ย
ย ย $RadioButton1.size = New-Object System.Drawing.Size(160,25)ย
ย ย $RadioButton1.Checked = $trueย
ย ย $RadioButton1.Text = "vCenter 1"ย
ย ย $groupBox.Controls.Add($RadioButton1)ย
ย ย $RadioButton2 = New-Object System.Windows.Forms.RadioButton
ย ย $RadioButton2.Location = new-object System.Drawing.Point(15,40)
ย ย $RadioButton2.size = New-Object System.Drawing.Size(160,25)
ย ย $RadioButton2.Text = "vCenter 2"
ย ย $groupBox.Controls.Add($RadioButton2)
ย ย #Select Snapshot memory
ย ย $groupBox1 = New-Object System.Windows.Forms.GroupBox
ย ย $groupBox1.Location = New-Object System.Drawing.Size(200,20)ย
ย ย $groupBox1.size = New-Object System.Drawing.Size(180,80)ย
ย ย $groupBox1.text = "Snapshot Memory:"ย
ย ย $Form.Controls.Add($groupBox1)ย
ย ย $RadioButton3 = New-Object System.Windows.Forms.RadioButtonย
ย ย $RadioButton3.Location = new-object System.Drawing.Point(15,15)ย
ย ย $RadioButton3.size = New-Object System.Drawing.Size(160,25)ย
ย ย $RadioButton3.Checked = $trueย
ย ย $RadioButton3.Text = "Snapshot Without Memory"ย
ย ย $groupBox1.Controls.Add($RadioButton3)ย
ย ย $RadioButton4 = New-Object System.Windows.Forms.RadioButton
ย ย $RadioButton4.Location = new-object System.Drawing.Point(15,40)
ย ย $RadioButton4.size = New-Object System.Drawing.Size(160,25)
ย ย $RadioButton4.Text = "Snapshot With Memory"
ย ย $groupBox1.Controls.Add($RadioButton4)
ย ย ย
ย ย $label = New-Object System.Windows.Forms.Label
ย ย $label.Location = New-Object System.Drawing.Size(390,20)ย
ย ย $label.Size = New-Object System.Drawing.Size(280,20)
ย ย $label.AutoSize = $true
ย ย $label.Text = "Enter Hostname Here..."
ย ย # Create the TextBox used to capture the user's text.
ย ย $textBox = New-Object System.Windows.Forms.TextBoxย
ย ย $textBox.Location = New-Object System.Drawing.Size(390,40)ย
ย ย $textBox.Size = New-Object System.Drawing.Size(130,230)
ย ย $textBox.AcceptsReturn = $true
ย ย $textBox.AcceptsTab = $false
ย ย $textBox.Multiline = $true
ย ย $textBox.ScrollBars = 'Both'
ย ย $textbox.CharacterCasing='Upper'
ย ย # Getting Input for Snapshot SR#, Snapshot Name, Snapshot Description.
ย ย $SR_label = New-Object System.Windows.Forms.Label
ย ย $SR_label.Location = New-Object System.Drawing.Size(15,130)ย
ย ย $SR_label.Size = New-Object System.Drawing.Size(280,20)
ย ย $SR_label.AutoSize = $true
ย ย $SR_label.Text = "*SR#"
ย ย $SR_textBox = New-Object System.Windows.Forms.TextBoxย
ย ย $SR_textBox.Location = New-Object System.Drawing.Size(150,130)ย
ย ย $SR_textBox.Size = New-Object System.Drawing.Size(130,20)
ย ย $SR_textBox.AcceptsReturn = $true
ย ย $SR_textBox.AcceptsTab = $false
ย ย $SR_textbox.CharacterCasing='Upper'
ย ย ย
ย ย $SN_label = New-Object System.Windows.Forms.Label
ย ย $SN_label.Location = New-Object System.Drawing.Size(15,160)ย
ย ย $SN_label.Size = New-Object System.Drawing.Size(280,20)
ย ย $SN_label.AutoSize = $true
ย ย $SN_label.Text = "*Snapshot Name"
ย ย ย
ย ย $SN_textBox = New-Object System.Windows.Forms.TextBoxย
ย ย $SN_textBox.Location = New-Object System.Drawing.Size(150,160)ย
ย ย $SN_textBox.Size = New-Object System.Drawing.Size(200,20)
ย ย $SN_textBox.AcceptsReturn = $true
ย ย $SN_textBox.AcceptsTab = $false
ย ย #$SN_textbox.CharacterCasing='Upper'
ย ย $SD_label = New-Object System.Windows.Forms.Label
ย ย $SD_label.Location = New-Object System.Drawing.Size(15,190)ย
ย ย $SD_label.Size = New-Object System.Drawing.Size(280,20)
ย ย $SD_label.AutoSize = $true
ย ย $SD_label.Text = "Snapshot Description"
ย ย $SD_textBox = New-Object System.Windows.Forms.TextBoxย
ย ย $SD_textBox.Location = New-Object System.Drawing.Size(150,190)ย
ย ย $SD_textBox.Size = New-Object System.Drawing.Size(200,50)
ย ย $SD_textBox.AcceptsReturn = $true
ย ย $SD_textBox.AcceptsTab = $false
ย ย $SD_textBox.Multiline = $true
ย ย $SD_textBox.ScrollBars = 'Both'
ย ย #$SD_textbox.CharacterCasing='Upper'
ย ย ย ย ย
ย ย #Create the Hardening Button.
ย ย $HButton = New-Object System.Windows.Forms.Button
ย ย $HButton.Location = New-Object System.Drawing.Size(540,40)
ย ย $HButton.Size = New-Object System.Drawing.Size(100,40)
ย ย $HButton.Text = "Take Snapshot"
ย ย #Create the Report Button.
ย ย $RButton = New-Object System.Windows.Forms.Button
ย ย $RButton.Location = New-Object System.Drawing.Size(540,90)
ย ย $RButton.Size = New-Object System.Drawing.Size(100,40)
ย ย $RButton.Text = "Generate Report"
ย ย #Create the Report Button.
ย ย $EmailButton = New-Object System.Windows.Forms.Button
ย ย $EmailButton.Location = New-Object System.Drawing.Size(540,140)
ย ย $EmailButton.Size = New-Object System.Drawing.Size(100,40)
ย ย $EmailButton.Text = "Send Email"
ย ย #Create the Progress-Bar.
ย ย $label1 = New-Object System.Windows.Forms.Label
ย ย $label1.Location = New-Object System.Drawing.Size(20,230)ย
ย ย $label1.Size = New-Object System.Drawing.Size(280,20)
ย ย $label1.AutoSize = $true
ย ย $label1.Text = "Progress..."
ย ย $PB = New-Object System.Windows.Forms.ProgressBar
$PB.Name = "PowerShellProgressBar"
$PB.Value = 0
$PB.Style="Continuous"
ย ย $System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 200 - 40
$System_Drawing_Size.Height = 20
$PB.Size = $System_Drawing_Size
$PB.Left = 20
$PB.Top = 250
ย ย ย
ย ย #Initiate Snapshot
ย ย $HButton.Add_Click(
ย ย {ย
ย ย ย ย $report= @()
ย ย ย ย $counter = 0
ย ย ย ย If ($SR_textBox.TextLength -ne 0 -and $SN_textBox.TextLength -ne 0)
ย ย ย ย {
ย ย ย ย ย ย ย ย $S_Name= $SR_textBox.text+ " - " +$SN_textBox.textย ย
ย ย ย ย }
ย ย ย ย Else{
ย ย ย ย ย ย [System.Windows.Forms.MessageBox]::Show("SR# or Snapshot Name cannot be blank", "Info")
ย ย ย ย ย ย return
ย ย ย ย }
ย ย
ย ย ย ย If ($textbox.TextLength -eq 0)
ย ย ย ย {
ย ย ย ย ย ย [System.Windows.Forms.MessageBox]::Show("Server List is empty", "Info")
ย ย ย ย ย ย returnย ย ย ย
ย ย ย ย }
ย ย ย ย [System.Windows.Forms.MessageBox]::Show("Sit back and relax while Snapshot is taken !!!", "VM Snapshot")
ย ย ย ย $ServerList=$textbox.Text.Split("`n")|%{$_.trim()}
ย ย ย ย Foreach ($vm in $ServerList)
ย ย ย ย {
ย ย ย ย ย ย if($vm -eq "")
ย ย ย ย ย ย {
ย ย ย ย ย ย ย ย $counter++
ย ย ย ย ย ย ย ย [Int]$Percentage = ($Counter/$ServerList.Count)*100
ย ย ย ย ย ย ย ย $PB.Value = $Percentage
ย ย ย ย ย ย ย ย continue
ย ย ย ย ย ย }
ย ย ย ย ย ย ย ย ย
ย ย ย ย ย ย if ($RadioButton1.Checked -eq $True)ย
ย ย ย ย ย ย {
ย ย ย ย ย ย ย ย Add-PSSnapin VMware.VimAutomation.Core
ย ย ย ย ย ย ย ย Connect-VIServer -Server vCenter1
ย ย ย ย ย ย }
ย ย ย ย ย ย if ($RadioButton2.Checked -eq $True)ย
ย ย ย ย ย ย {
ย ย ย ย ย ย ย ย Add-PSSnapin VMware.VimAutomation.Core
ย ย ย ย ย ย ย ย Connect-VIServer -Server vCenter2
ย ย ย ย ย ย }
ย ย ย ย ย ย $Exists = get-vm -name $vm -ErrorAction SilentlyContinue
ย ย ย ย ย ย If ($Exists)
ย ย ย ย ย ย {
ย ย ย ย ย ย ย ย If ($RadioButton3.Checked -eq $True)
ย ย ย ย ย ย ย ย {
ย ย ย ย ย ย ย ย ย ย Get-VM $vm |New-snapshot -Name $S_Name -Description $SD_textBox.Text
ย ย ย ย ย ย ย ย ย ย $rep = Get-VM $vm | Get-Snapshot | Select-Object @{Name='VM';Expression={$_.vm}},@{Name='Snapshot_Name';Expression={$_.name}},@{Name='Description';Expression={$_.Description}},@{Name='Created';Expression={$_.Created}},@{Name='Remarks';Expression={""}}
ย ย ย ย ย ย ย ย ย ย $report = $report + $rep
ย ย ย ย ย ย ย ย }
ย ย ย ย ย ย ย ย If ($RadioButton4.Checked -eq $True)
ย ย ย ย ย ย ย ย {
ย ย ย ย ย ย ย ย ย ย Get-VM $vm |New-snapshot -Name $S_Name -Description $SD_textBox.Text -Memory
ย ย ย ย ย ย ย ย ย ย $rep = Get-VM $vm | Get-Snapshot | Select-Object @{Name='VM';Expression={$_.vm}},@{Name='Snapshot_Name';Expression={$_.name}},@{Name='Description';Expression={$_.Description}},@{Name='Created';Expression={$_.Created}},@{Name='Remarks';Expression={""}}
ย ย ย ย ย ย ย ย ย ย $report = $report + $rep
ย ย ย ย ย ย ย ย }
ย ย ย ย ย ย }
ย ย ย ย ย ย If (!$Exists)
ย ย ย ย ย ย {
ย ย ย ย ย ย ย ย $row= New-Object PSObject -Property @{VM = $vm;Snapshot_Name = "";Description = "";Created = "";Remarks="Server not Found"}
ย ย ย ย ย ย ย ย $report += $row
ย ย ย ย ย ย }
ย ย ย ย ย ย $counter++
ย ย ย ย ย ย [Int]$Percentage = ($Counter/$ServerList.Count)*100
ย ย ย ย ย ย $PB.Value = $Percentage
ย ย ย ย }
ย ย ย ย [System.Windows.Forms.MessageBox]::Show("Snapshot taken successfully" , "Report Generation")
ย ย ย ย $report |Select-object @{Name="HOSTNAME"; Expression={$_.VM}},@{Name="Snapshot_Name"; Expression={$_.Snapshot_Name}},@{Name="Description"; Expression={$_.Description}},@{Name="Created: Date & Time"; Expression={$_.Created}},@{Name='Remarks';Expression={$_.Remarks}}| Export-Csv $file -NoTypeInformation
ย ย })
ย ย #Report Generation
ย ย $RButton.Add_Click(
ย ย {ย ย ย
ii $path
ย ย })
ย ย #Send Email
ย ย $EmailButton.Add_Click(
ย ย {ย ย
ย ย ย ย #Create Label
ย ย ย ย $ToLabel = New-Object System.Windows.Forms.Label
ย ย ย ย $ToLabel.Location = New-Object System.Drawing.Size(20,20)ย
ย ย ย ย $ToLabel.Size = New-Object System.Drawing.Size(100,20)
ย ย ย ย $ToLabel.AutoSize = $true
ย ย ย ย $ToLabelFont = New-Object Drawing.Font("Times New Roman",12,[System.Drawing.FontStyle]::Bold)
ย ย ย ย $ToLabel.Font = $ToLabelFont
ย ย ย ย $ToLabel.Text = "*To:"
ย ย ย ย $CcLabel = New-Object System.Windows.Forms.Label
ย ย ย ย $CcLabel.Location = New-Object System.Drawing.Size(20,50)ย
ย ย ย ย $CcLabel.Size = New-Object System.Drawing.Size(100,20)
ย ย ย ย $CcLabel.AutoSize = $true
ย ย ย ย $CcLabelFont = New-Object Drawing.Font("Times New Roman",12,[System.Drawing.FontStyle]::Bold)
ย ย ย ย $CcLabel.Font = $CcLabelFont
ย ย ย ย $CcLabel.Text = "Cc: (Optional)"
ย ย ย ย #Create the TextBox Email Address.
ย ย ย ย $ToBox = New-Object System.Windows.Forms.TextBoxย
ย ย ย ย $ToBox.Location = New-Object System.Drawing.Size(140,20)ย
ย ย ย ย $ToBox.Size = New-Object System.Drawing.Size(250,20)
ย ย ย ย $ToBoxFont = New-Object Drawing.Font("Times New Roman",8)
ย ย ย ย $ToBox.Font=$ToBoxFont
ย ย ย ย $ToBox.AcceptsReturn = $true
ย ย ย ย $ToBox.AcceptsTab = $false
ย ย ย ย #$ToBox.text=""
ย ย ย ย $ToBox.CharacterCasing='lower'
ย ย ย ย $CcBox = New-Object System.Windows.Forms.TextBoxย
ย ย ย ย $CcBox.Location = New-Object System.Drawing.Size(140,50)ย
ย ย ย ย $CcBox.Size = New-Object System.Drawing.Size(250,20)
ย ย ย ย $CcBoxFont = New-Object Drawing.Font("Times New Roman",8)
ย ย ย ย $CcBox.Font=$CcBoxFont
ย ย ย ย $CcBox.AcceptsReturn = $true
ย ย ย ย $CcBox.AcceptsTab = $false
ย ย ย ย $CcBox.text=""
ย ย ย ย $CcBox.CharacterCasing='lower'
ย ย ย ย ย
ย ย ย ย #Create Email Send Button
ย ย ย ย $SendButton = New-Object System.Windows.Forms.Button
ย ย ย ย $SendButton.Location = New-Object System.Drawing.Size(20,100)
ย ย ย ย $SendButton.Size = New-Object System.Drawing.Size(100,40)
ย ย ย ย $ButtonFont = New-Object Drawing.Font("Times New Roman",8,[System.Drawing.FontStyle]::Bold)
ย ย ย ย $SendButton.Font=$ButtonFont
ย ย ย ย $SendButton.Text = "Send Email"
ย ย ย ย $CancelButton = New-Object System.Windows.Forms.Button
ย ย ย ย $CancelButton.Location = New-Object System.Drawing.Size(150,100)
ย ย ย ย $CancelButton.Size = New-Object System.Drawing.Size(100,40)
ย ย ย ย $CancelButton.Font=$ButtonFont
ย ย ย ย $CancelButton.Text = "Cancel"
ย ย ย ย $Emailform.Controls.Add($ToLabel)
ย ย ย ย $Emailform.Controls.Add($CcLabel)
ย ย ย ย $Emailform.Controls.Add($ToBox)
ย ย ย ย $Emailform.Controls.Add($CCBox)
ย ย ย ย $Emailform.Controls.Add($SendButton)
ย ย ย ย $Emailform.Controls.Add($CancelButton)
ย ย ย ย ย
ย ย ย ย $SendButton.Add_Click(
ย ย ย ย {
ย ย ย ย ย ย If ($ToBox.Text.Length -eq 0)
ย ย ย ย ย ย {
ย ย ย ย ย ย ย ย [System.Windows.Forms.MessageBox]::Show("Email address cannot be empty" , "Email Info")
ย ย ย ย ย ย ย ย return
ย ย ย ย ย ย }
ย ย ย ย ย ย #---------------------------------------------------------------------
ย ย ย ย ย ย # Generate the HTML report and output to file
ย ย ย ย ย ย #---------------------------------------------------------------------
ย ย ย ย ย ย $head = "<style>"
ย ย ย ย ย ย $head = $head + "BODY{background-color:white;}"
ย ย ย ย ย ย $head = $head + "TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}"
ย ย ย ย ย ย $head = $head + "TH{border-width: 1px;padding: 0px;border-style: solid;border-color: black;background-color:#778899}"
ย ย ย ย ย ย $head = $head + "TD{border-width: 1px;padding: 0px;border-style: solid;border-color: black}"
ย ย ย ย ย ย $head = $head + "</style>"
ย ย ย ย ย ย # SMTP info
ย ย ย ย ย ย $Toemail=$ToBox.Text
ย ย ย ย ย ย $strTo=$Toemail
ย ย ย ย ย ย $strCc=$CCBox.Text
ย ย ย ย ย ย $strSubject = "Snapshot Taken : $S_Name"
ย ย ย ย ย ย $StrMsg="Hi All, <br>Snapshot has been taken successfully for below list of servers <br><br>"
ย ย ย ย ย ย $strBody = "Attached is the list of Snapshots"
ย ย ย ย ย ย $strMail = $strmsg
ย ย ย ย ย ย # Write the output to an HTML file
ย ย ย ย ย ย $strOutFile = $Path+"email.html"
ย ย ย ย ย ย $ComputerName=(Get-WmiObject -Class Win32_ComputerSystem -Property Name).Name
ย ย ย ย ย ย Get-Content -Path $File |ConvertFrom-CSV | ConvertTo-HTMLย -Head $head -Body $StrMsg | Out-File $StrOutFile
ย ย ย ย ย ย ย
ย ย ย ย # Mail the output file
ย ย ย ย $msg = new-object Net.Mail.MailMessage
ย ย ย ย $att = new-object Net.Mail.Attachment($File)
ย ย ย ย $smtp = new-object Net.Mail.SmtpClient("SMTP Server")
ย ย ย ย ย ย $msg.From ="From@domain.com"
ย ย ย ย $msg.To.Add($strTo)
ย ย ย ย ย ย If ($strCc.Length -ne 0)
ย ย ย ย ย ย {
ย ย ย ย ย ย ย ย $msg.cc.Add($strcc)
ย ย ย ย ย ย }
ย ย ย ย $msg.Subject = $strSubject
ย ย ย ย $msg.IsBodyHtml = 1
ย ย ย ย $msg.Body = Get-Content $strOutFile
ย ย ย ย $msg.Attachments.Add($att)
ย ย ย ย ย ย $smtp.Send($msg)
ย ย ย ย ย ย ย ย [System.Windows.Forms.MessageBox]::Show("Email Send !!!","Info")
ย ย ย ย ย ย ย ย $Emailform.close()
ย ย ย ย })
ย ย ย ย $CancelButton.Add_Click(
ย ย ย ย {
ย ย ย ย ย ย $Emailform.close()
ย ย ย ย })
ย ย ย ย $Emailform.Add_Shown({$Emailform.Activate()})
ย ย ย ย $Emailform.ShowDialog() > $nullย
ย ย ย ย ย ย ย ย
ย ย })
ย ย ย ย ย
ย ย # Add all of the controls to the form.
ย ย $form.Controls.Add($label)
ย ย $form.Controls.Add($textBox)
ย ย $form.Controls.Add($groupBox)
ย ย $form.Controls.Add($groupBox1)
ย ย $form.Controls.Add($RButton)
ย ย $form.Controls.Add($HButton)
ย ย $form.Controls.Add($label1)
ย ย $form.Controls.Add($SR_label)
ย ย $form.Controls.Add($SR_textBox)
ย ย $form.Controls.Add($SN_label)
ย ย $form.Controls.Add($SN_textBox)
ย ย $form.Controls.Add($SD_label)
ย ย $form.Controls.Add($SD_textBox)
ย ย $form.Controls.Add($PB)
ย ย $form.Controls.Add($EmailButton)
ย ย # Initialize and show the form.
ย ย $form.Add_Shown({$form.Activate()})
ย ย $form.ShowDialog() > $null
}
Set-ExecutionPolicy unrestricted -Force
$date=get-Date -format "ddMMyy_HHmm"
$ComputerName=(Get-WmiObject -Class Win32_ComputerSystem -Property Name).Name
$Path=[System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::Desktop)+"\Report\"
If ((Test-Path $Path) -eq $false)
{
ย ย New-Item $Path -type directory
}
New-Item -ErrorAction Ignore -ItemType directory -Path Report
$File=$Path + "SnapshotReport_$date.csv"
Snapshot</pre>
<!-- /wp:syntaxhighlighter/code -->
๐ก Why this is a “Lazy” Win:
- Tab Isolation: You won’t accidentally delete snapshots while trying to create them.
- RunAsync Switch: I added
-RunAsyncto the deletion. This means the GUI won’t “freeze” while vCenter is doing the heavy lifting of disk consolidation. - Standardized Cleanup: By hardcoding
$Days = 2, you ensure that your team follows a consistent 48-hour retention policy.
Exporting PSTs and Setting Up External Forwarding | Lazy Admin Blog

As a “Lazy Admin,” I like to keep my cheat sheets handy so I donโt have to look up the same syntax every time a request comes in. Todayโs snippets cover exporting mailboxes to PST and the “correct” way to forward mail to an external address.
1. Exporting Mailboxes to PST
Whether you are decommissioning a user or just taking a backup, New-MailboxExportRequest is your best friend.
Note: Ensure your Exchange Trusted Subsystem has read/write permissions on the network share before running these.
Export a Standard Mailbox:
New-MailboxExportRequest -Mailbox "Mailbox Name" -FilePath \\Sharelocation\Export.pst -DomainController DC_FQDN
Export a Mailbox Archive:
If the user has an In-Place Archive enabled, use the -IsArchive switch:
New-MailboxExportRequest -Mailbox "Mailbox Name" -IsArchive -FilePath \\Sharelocation\Archive.pst -DomainController DC_FQDN
2. Reconnecting a Disconnected Mailbox
If you have a disconnected mailbox that needs to be linked to a user account:
Connect-Mailbox -Identity "Mailbox Name" -Database DBName -User "UserName"
3. Forwarding Mail Externally (The Right Way)
To forward mail to an external address (like a Gmail or Outlook account), you shouldn’t just type an address into a transport rule. The cleanest way is to create a Mail Contact.
Step A: Create the Contact
- Login to Exchange Admin Center > Recipients > Contacts.
- Click Add > Mail Contact.
- Give it a sensible name.

Pro Tip: If you don’t want this contact cluttering up your Global Address List (GAL), hide it with PowerShell:
Set-MailContact "ALIAS-NAME" -HiddenFromAddressListsEnabled $true

Step B: Enable Forwarding via GUI

- Go to Recipients > Mailboxes.
- Locate the user you want to forward from and click Edit.
- Go to Mailbox Features > Scroll down to Mail Flow > View Details.
- Check Enable Forwarding.
- Browse and select the Contact you created in Step A.
- Optional: Check “Deliver message to both forwarding address and mailbox” if you want a copy kept in the original inbox.

Step C: Enable Forwarding via PowerShell (The Lazy Way)
If you don’t want to click through the GUI, just run this:
Set-Mailbox -Identity "Internal User" -ForwardingAddress "ExternalContactAlias" -DeliverToMailboxAndForward $true





![IC347745[1]](https://i0.wp.com/lazyadminblog.com/wp-content/uploads/2015/05/ic3477451.gif?resize=300%2C190&ssl=1)

![pic1[1]](https://i0.wp.com/lazyadminblog.com/wp-content/uploads/2015/05/pic11.png?resize=233%2C300&ssl=1)
![pic2[1]](https://i0.wp.com/lazyadminblog.com/wp-content/uploads/2015/05/pic21.png?resize=300%2C209&ssl=1)
![pic8[1]](https://i0.wp.com/lazyadminblog.com/wp-content/uploads/2015/05/pic81.png?resize=266%2C300&ssl=1)
![pic9[1]](https://i0.wp.com/lazyadminblog.com/wp-content/uploads/2015/05/pic91.png?resize=300%2C155&ssl=1)
![pic10[1]](https://i0.wp.com/lazyadminblog.com/wp-content/uploads/2015/05/pic101.png?resize=300%2C122&ssl=1)
![pic11[1]](https://i0.wp.com/lazyadminblog.com/wp-content/uploads/2015/05/pic111.png?resize=247%2C300&ssl=1)
![3348.diskcleanup3[1]](https://i0.wp.com/lazyadminblog.com/wp-content/uploads/2015/05/3348-diskcleanup31.png?resize=244%2C300&ssl=1)
![pic12[1]](https://i0.wp.com/lazyadminblog.com/wp-content/uploads/2015/05/pic121.png?resize=230%2C300&ssl=1)
![lockoutstatus[1]](https://i0.wp.com/lazyadminblog.com/wp-content/uploads/2015/04/lockoutstatus1.jpg?resize=300%2C97&ssl=1)