Reposting Robin Hobo, follow below guide to deploy windows Auto Patch with Intune
Thank you Robin Hobo for providing an awesome guide
Virtualization - Cloud
Reposting Robin Hobo, follow below guide to deploy windows Auto Patch with Intune
Thank you Robin Hobo for providing an awesome guide
I have recently executed Citrix Virtual Apps & Desktop POC on Nutanix Hypervisor (AHV) platform, writing this post to share my technical knowledge gained in this project.
Project Goal:
Refer the below high level design document which contains Prerequisites, Dependencies, Hardware Requirements, VDI Models, NVIDIA requirements etc..
Hardware Installation Checklist to install Nutanix for Citrix POC
Below IP’s s are posted here for example/reference for better understanding
Refer below document for installing Nutanix AHV Cluster on HP Servers
Post Installation of Nutanix, Installed Citrix Infrastructure components. I am not covering these installation steps in this post as many articles are available on internet.
This POC has a requirement of GPU workloads hence we have installed NVIDIA License server & NVIDIA Graphic drivers on host & guest machines. Follow below document to know the NVIDIA Installation procedure
This POC has a requirement of delivering Linux VDI workloads for CAD/CAM applications, Follow below post to know the VDA & Graphics installation procedure for Linux
Knowledge Base References
Note: This post will be updated with few more updates
Inspired from https://james-rankin.com/articles/spreading-users-over-multiple-file-shares-with-fslogix-profile-containers/
James Rankin blog script created based on free space criteria but our requirement is based on user count. Below is the snippet for your reference
<# This script created for spreading users over multiple file shares with FSLogix Profile Containers
With Ref https://james-rankin.com/articles/spreading-users-over-multiple-file-shares-with-fslogix-profile-containers/
Script customized as per Citrix VDI requirement, Azure Subscription will be connected with User Assigned Managed Idenity(UAMI) and UAMI is assigned to Citrix Master Image.
Script is added in Task Scheduler of Master Image and Machine catalog is created with "Machine Profile" option so that all cloned VDI's get UAMI property #>
# This script count the directories in each share and FSLOgix VHD location is added based on count retrieved. We took directories count as users count can not be measured.
# This Script also created to address the Azure File Handle limit which is 10k per share
# This script fetches directories count from MULTIPLE AZURE FILE STORAGES AND MULTIPLE SHARES
########### Set TLS 1.2 ###########
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
########### Connect Using User Assigned Managed Identity (UAMI) ###########
Connect-AzAccount -Identity -AccountId <UAMI Client ID>
########### Provide Profile Storage Account Details ###########
# Provide "Profile Storage Account Name and Storage Resource Group Name" else LEAVE BLANK values
$profilestorageaccount1 = ""
$profilestorageaccount1RG = ""
$profilestorageaccount1shares = @(
"profile1",
"profile2"
)
# Provide "Profile Storageaccount Shares FULL PATH" in above SHARES ORDER ONLY
$profilestorageaccount1sharesfullpath = @(
"\\<storageaccountname>.file.core.windows.net\profile1",
"\\<storageaccountname>.file.core.windows.net\profile2"
)
# Provide "Profile Storage Account Name and Storage Resource Group Name" else LEAVE BLANK values
$profilestorageaccount2 = ""
$profilestorageaccount2RG = ""
$profilestorageaccount2shares = @(
"profile3",
"profile4"
)
# Provide "Profile Storageaccount Shares FULL PATH" in above SHARES ORDER ONLY
$profilestorageaccount2sharesfullpath = @(
"\\<storageaccountname>.file.core.windows.net\profile3",
"\\<storageaccountname>c.file.core.windows.net\profile4"
)
# Provide "Profile Storage Account Name and Storage Resource Group Name" else LEAVE BLANK values
$profilestorageaccount3 = ""
$profilestorageaccount3RG = ""
$profilestorageaccount3shares = @(
)
# Provide "Profile Storageaccount Shares FULL PATH" in above SHARES ORDER ONLY
$profilestorageaccount3sharesfullpath = @(
)
# Provide "Profile Storage Account Name and Storage Resource Group Name" else LEAVE BLANK values
$profilestorageaccount4 = ""
$profilestorageaccount4RG = ""
$profilestorageaccount4shares = @(
)
# Provide "Profile Storageaccount Shares FULL PATH" in above SHARES ORDER ONLY
$profilestorageaccount4sharesfullpath = @(
)
# Provide "Profile Storage Account Name and Storage Resource Group Name" else LEAVE BLANK values
$profilestorageaccount5 = ""
$profilestorageaccount5RG = ""
$profilestorageaccount5shares = @(
)
# Provide "Profile Storageaccount Shares FULL PATH" in above SHARES ORDER ONLY
$profilestorageaccount5sharesfullpath = @(
)
########### Provide Office Storage Account Details ###########
# Provide "Office Storage Account Name and Storage Resource Group Name" else LEAVE BLANK values
$officestorageaccount1 = ""
$officestorageaccount1RG = ""
$officestorageaccount1shares = @(
"office1",
"office2"
)
# Provide "Office Storageaccount Shares FULL PATH" in above SHARES ORDER ONLY
$officestorageaccount1sharesfullpath = @(
"\\<storageaccountname>.file.core.windows.net\office1",
"\\<storageaccountname>.file.core.windows.net\office2"
)
# Provide "Office Storage Account Name and Storage Resource Group Name" else LEAVE BLANK values
$officestorageaccount2 = ""
$officestorageaccount2RG = ""
$officestorageaccount2shares = @(
"office3",
"office4"
)
# Provide "Office Storageaccount Shares FULL PATH" in above SHARES ORDER ONLY
$officestorageaccount2sharesfullpath = @(
"\\<storageaccountname>.file.core.windows.net\office3",
"\\<storageaccountname>.file.core.windows.net\office4"
)
# Provide "Office Storage Account Name and Storage Resource Group Name" else LEAVE BLANK values
$officestorageaccount3 = ""
$officestorageaccount3RG = ""
$officestorageaccount3shares = @(
)
# Provide "Office Storageaccount Shares FULL PATH" in above SHARES ORDER ONLY
$officestorageaccount3sharesfullpath = @(
)
# Provide "Office Storage Account Name and Storage Resource Group Name" else LEAVE BLANK values
$officestorageaccount4 = ""
$officestorageaccount4RG = ""
$officestorageaccount4shares = @(
)
# Provide "Office Storageaccount Shares FULL PATH" in above SHARES ORDER ONLY
$officestorageaccount4sharesfullpath = @(
)
# Provide "Office Storage Account Name and Storage Resource Group Name" else LEAVE BLANK values
$officestorageaccount5 = ""
$officestorageaccount5RG = ""
$officestorageaccount5shares = @(
)
# Provide "Office Storageaccount Shares FULL PATH" in above SHARES ORDER ONLY
$officestorageaccount5sharesfullpath = @(
)
########### Fetch the Directories in the Profile Shares ###########
$ProfileDirShareCount = @()
$profileorderedShares = @()
if($profilestorageaccount1shares.Count -gt 0)
{
$i = 0
$keys = Get-AzStorageAccountKey -ResourceGroupName $profilestorageaccount1RG -AccountName $profilestorageaccount1
$value = $keys[0].Value
$context = New-AzStorageContext -StorageAccountName $profilestorageaccount1 -StorageAccountKey $value
foreach($sharename in $profilestorageaccount1shares)
{
$directories = Get-AzStorageFile -ShareName $sharename -Context $context
$dircount = $directories.count
$shareSpace = New-Object -TypeName psobject
$path = $profilestorageaccount1sharesfullpath[$i]
$sharespace | Add-Member -membertype NoteProperty -Name Share -value $path
$sharespace | Add-Member -membertype NoteProperty -Name freespace -value $dircount
$ProfileDirShareCount += $shareSpace
$i = $i + 1
}
}
if($profilestorageaccount2shares.Count -gt 0)
{
$i = 0
$keys = Get-AzStorageAccountKey -ResourceGroupName $profilestorageaccount2RG -AccountName $profilestorageaccount2
$value = $keys[0].Value
$context = New-AzStorageContext -StorageAccountName $profilestorageaccount2 -StorageAccountKey $value
foreach($sharename in $profilestorageaccount2shares)
{
$directories = Get-AzStorageFile -ShareName $sharename -Context $context
$dircount = $directories.count
$shareSpace = New-Object -TypeName psobject
$path = $profilestorageaccount2sharesfullpath[$i]
$sharespace | Add-Member -membertype NoteProperty -Name Share -value $path
$sharespace | Add-Member -membertype NoteProperty -Name freespace -value $dircount
$ProfileDirShareCount += $shareSpace
$i = $i + 1
}
}
if($profilestorageaccount3shares.Count -gt 0)
{
$i = 0
$keys = Get-AzStorageAccountKey -ResourceGroupName $profilestorageaccount3RG -AccountName $profilestorageaccount3
$value = $keys[0].Value
$context = New-AzStorageContext -StorageAccountName $profilestorageaccount3 -StorageAccountKey $value
foreach($sharename in $profilestorageaccount3shares)
{
$directories = Get-AzStorageFile -ShareName $sharename -Context $context
$dircount = $directories.count
$shareSpace = New-Object -TypeName psobject
$path = $profilestorageaccount3sharesfullpath[$i]
$sharespace | Add-Member -membertype NoteProperty -Name Share -value $path
$sharespace | Add-Member -membertype NoteProperty -Name freespace -value $dircount
$ProfileDirShareCount += $shareSpace
$i = $i + 1
}
}
if($profilestorageaccount4shares.Count -gt 0)
{
$i = 0
$keys = Get-AzStorageAccountKey -ResourceGroupName $profilestorageaccount4RG -AccountName $profilestorageaccount4
$value = $keys[0].Value
$context = New-AzStorageContext -StorageAccountName $profilestorageaccount4 -StorageAccountKey $value
foreach($sharename in $profilestorageaccount4shares)
{
$directories = Get-AzStorageFile -ShareName $sharename -Context $context
$dircount = $directories.count
$shareSpace = New-Object -TypeName psobject
$path = $profilestorageaccount4sharesfullpath[$i]
$sharespace | Add-Member -membertype NoteProperty -Name Share -value $path
$sharespace | Add-Member -membertype NoteProperty -Name freespace -value $dircount
$ProfileDirShareCount += $shareSpace
$i = $i + 1
}
}
if($profilestorageaccount5shares.Count -gt 0)
{
$i = 0
$keys = Get-AzStorageAccountKey -ResourceGroupName $profilestorageaccount5RG -AccountName $profilestorageaccount5
$value = $keys[0].Value
$context = New-AzStorageContext -StorageAccountName $profilestorageaccount5 -StorageAccountKey $value
foreach($sharename in $profilestorageaccount5shares)
{
$directories = Get-AzStorageFile -ShareName $sharename -Context $context
$dircount = $directories.count
$shareSpace = New-Object -TypeName psobject
$path = $profilestorageaccount5sharesfullpath[$i]
$sharespace | Add-Member -membertype NoteProperty -Name Share -value $path
$sharespace | Add-Member -membertype NoteProperty -Name freespace -value $dircount
$ProfileDirShareCount += $shareSpace
$i = $i + 1
}
}
$SortedProfileShares = $ProfileDirShareCount | Sort-Object freespace | select share
foreach ($item in $SortedProfileShares) {
$profileorderedShares += $item.Share.ToString()
}
########### Fetch the Directories in the Office Shares ###########
$OfficeDirShareCount = @()
$officeorderedShares = @()
if($officestorageaccount1shares.Count -gt 0)
{
$i = 0
$keys = Get-AzStorageAccountKey -ResourceGroupName $officestorageaccount1RG -AccountName $officestorageaccount1
$value = $keys[0].Value
$context = New-AzStorageContext -StorageAccountName $officestorageaccount1 -StorageAccountKey $value
foreach($sharename in $officestorageaccount1shares)
{
$directories = Get-AzStorageFile -ShareName $sharename -Context $context
$dircount = $directories.count
$shareSpace = New-Object -TypeName psobject
$path = $officestorageaccount1sharesfullpath[$i]
$sharespace | Add-Member -membertype NoteProperty -Name Share -value $path
$sharespace | Add-Member -membertype NoteProperty -Name freespace -value $dircount
$officeDirShareCount += $shareSpace
$i = $i + 1
}
}
if($officestorageaccount2shares.Count -gt 0)
{
$i = 0
$keys = Get-AzStorageAccountKey -ResourceGroupName $officestorageaccount2RG -AccountName $officestorageaccount2
$value = $keys[0].Value
$context = New-AzStorageContext -StorageAccountName $officestorageaccount2 -StorageAccountKey $value
foreach($sharename in $officestorageaccount2shares)
{
$directories = Get-AzStorageFile -ShareName $sharename -Context $context
$dircount = $directories.count
$shareSpace = New-Object -TypeName psobject
$path = $officestorageaccount2sharesfullpath[$i]
$sharespace | Add-Member -membertype NoteProperty -Name Share -value $path
$sharespace | Add-Member -membertype NoteProperty -Name freespace -value $dircount
$officeDirShareCount += $shareSpace
$i = $i + 1
}
}
if($officestorageaccount3shares.Count -gt 0)
{
$i = 0
$keys = Get-AzStorageAccountKey -ResourceGroupName $officestorageaccount3RG -AccountName $officestorageaccount3
$value = $keys[0].Value
$context = New-AzStorageContext -StorageAccountName $officestorageaccount3 -StorageAccountKey $value
foreach($sharename in $officestorageaccount3shares)
{
$directories = Get-AzStorageFile -ShareName $sharename -Context $context
$dircount = $directories.count
$shareSpace = New-Object -TypeName psobject
$path = $officestorageaccount3sharesfullpath[$i]
$sharespace | Add-Member -membertype NoteProperty -Name Share -value $path
$sharespace | Add-Member -membertype NoteProperty -Name freespace -value $dircount
$officeDirShareCount += $shareSpace
$i = $i + 1
}
}
if($officestorageaccount4shares.Count -gt 0)
{
$i = 0
$keys = Get-AzStorageAccountKey -ResourceGroupName $officestorageaccount4RG -AccountName $officestorageaccount4
$value = $keys[0].Value
$context = New-AzStorageContext -StorageAccountName $officestorageaccount4 -StorageAccountKey $value
foreach($sharename in $officestorageaccount4shares)
{
$directories = Get-AzStorageFile -ShareName $sharename -Context $context
$dircount = $directories.count
$shareSpace = New-Object -TypeName psobject
$path = $officestorageaccount4sharesfullpath[$i]
$sharespace | Add-Member -membertype NoteProperty -Name Share -value $path
$sharespace | Add-Member -membertype NoteProperty -Name freespace -value $dircount
$officeDirShareCount += $shareSpace
$i = $i + 1
}
}
if($officestorageaccount5shares.Count -gt 0)
{
$i = 0
$keys = Get-AzStorageAccountKey -ResourceGroupName $officestorageaccount5RG -AccountName $officestorageaccount5
$value = $keys[0].Value
$context = New-AzStorageContext -StorageAccountName $officestorageaccount5 -StorageAccountKey $value
foreach($sharename in $officestorageaccount5shares)
{
$directories = Get-AzStorageFile -ShareName $sharename -Context $context
$dircount = $directories.count
$shareSpace = New-Object -TypeName psobject
$path = $officestorageaccount5sharesfullpath[$i]
$sharespace | Add-Member -membertype NoteProperty -Name Share -value $path
$sharespace | Add-Member -membertype NoteProperty -Name freespace -value $dircount
$officeDirShareCount += $shareSpace
$i = $i + 1
}
}
$SortedOfficeShares = $OfficeDirShareCount | Sort-Object freespace | select share
foreach ($item in $SortedOfficeShares) {
$officeorderedShares += $item.Share.ToString()
}
########### Remove the Existing VHDLocations Available ###########
$FSLogixProfilePath="HKLM:\software\FSLogix\Profiles"
$FSLogixODFCPath="HKLM:\SOFTWARE\Policies\FSLogix\ODFC"
$FSLogixKeyName="VHDLocations"
if ((get-item -path $FSLogixProfilePath).GetValue($FSLogixKeyName) -ne $null) {
Remove-itemProperty -path $FSLogixProfilePath -Name $FSLogixKeyName -force
} else {
# do nothing, no key to delete
}
if ((get-item -path $FSLogixODFCPath).GetValue($FSLogixKeyName) -ne $null) {
Remove-itemProperty -path $FSLogixODFCPath -Name $FSLogixKeyName -force
} else {
# do nothing, no key to delete
}
########### Adding New VHDLocation for Profile ###########
### Reason for changing VHDLocation property type to Multi string value is because that FSLogix need to searches the user profile in all shares hence to keep all shares , regkey should be Mutistring
New-ItemProperty $FSLogixProfilePath -Name $FSLogixKeyName -Value $profileorderedShares -PropertyType MultiString -Force
########### Adding New VHDLocation for Office ###########
New-ItemProperty $FSLogixODFCPath -Name $FSLogixKeyName -Value $officeorderedShares -PropertyType MultiString -Force
########### Write OutPut ###########
$folderPath="C:\Temp"
$ProfileDirShareCount | Export-csv -Path "$folderPath\Profile_Shareleastlog_$((Get-Date).ToString('MM-dd-yyyy_hh-mm-ss')).csv" -NoTypeInformation
$OfficeDirShareCount | Export-csv -Path "$folderPath\Office_Shareleastlog_$((Get-Date).ToString('MM-dd-yyyy_hh-mm-ss')).csv" -NoTypeInformation
Below script is used to delete user profiles based on user logon names
# Delete the Files in Azure file share by taking input value GPN(logon) which is provided in CSV
### Az.Accounts, Az.Resources & Az.Storage modules are required to execute the script.
# To Set TLS1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$path="C:\Temp" ### Path for input & output files
$NTRUsers=Import-Csv $path\NTRUsersGPN.csv # Input File with UserLogonNames
write-host "NTRUsersGPN Count = $($NTRUsers.Count)"
## Connect Azure Subscription with your own method
# Connect-AzAccount -AccessToken
#FileShare Details from which the user profiles needs to removed
$StorageAccountName="<Name>"
$fileShareName="<Share Name" # Only one fileshare at a time, to avoid the deletion of profiles in different shares.
$ResourceGroup = (Get-AzResource -Name $StorageAccountName).ResourceGroupName
$Key = Get-AzStorageAccountKey -ResourceGroupName $ResourceGroup -Name $StorageAccountName
$context=New-AzStorageContext -StorageAccountName $StorageAccountName -StorageAccountKey $Key[1].Value
$finalResult=@()
$FilesDirectory=Get-AzStorageFile -Context $context -ShareName $fileShareName
write-host "FilesDirectory Count = $($FilesDirectory.Count)"
$i=1
foreach($FilesD in $FilesDirectory)
{
foreach($NTRUser in $NTRUsers)
{
if($FilesD.Name -cmatch $NTRUser.UserGPN)
{
try
{
$Files=$FilesD|Get-AzStorageFile
foreach($File in $Files)
{
Remove-AzStorageFile -Context $context -ShareName $fileShareName -Path $File.ShareFileClient.Path
}
Remove-AzStorageDirectory -Context $context -ShareName $fileShareName -Path $FilesD.Name
$obj=""|select User,Directory,Status
$obj.User=$NTRUser.UserGPN
$obj.Directory=$FileD.Name
$obj.Status="Success"
$finalResult+=$obj
}
catch
{
$obj=""|select User,Directory,Status
$obj.User=$NTRUser.UserGPN
$obj.Directory=$FileD.Name
$obj.Status=$_.Exception.Message
$finalResult+=$obj
}
}
}
$i=$i+1
}
$finalResult | Export-Csv -Path "$path\$($fileShareName)FileSharesDeleteOutput.csv" -Append -Force -NoTypeInformation
Post Virtual Machine Deletion in Azure, few resources like Disk,NICs,NSG,Public IP etc.. will remain exists so use below script to cleanup all these orphaned resources
## Gather orphaned resources details using below link and use below script to delete all orphaned resources
https://github.com/scautomation/AzureResourceGraph-Examples/blob/master/resourceQueries/Orphaned%20Resources/OrphanedResource.MD
##########################DeleteOrphanedDisk##########################
$csv = Import-Csv "disk.csv" #name of csv file
$filetim = (Get-Date).tostring("dd-MM-yyyy-hh-mm")
$Logfile = "log-deletedisk-$filetim.log"
Function LogWrite
{
Param ([string]$logstring)
Add-content $Logfile -value $logstring
}
Start-Transcript -Path $Logfile
Stop-Transcript
LogWrite " "
LogWrite " "
LogWrite "***** LogFile *****"
LogWrite " "
LogWrite " "
$csv | ForEach-Object {
$diskname = $_.NAME
$state = $_.DISKSTATE
$resourcegroup = $_.RESOURCEGROUP
$location = $_.LOCATION
if($state -like "Unattached")
{
$tim = (Get-Date).tostring("dd-MM-yyyy-hh:mm:ss")
LogWrite "$tim - Deleting disk $diskname in $resourcegroup of Location - $location"
Write-Host -ForegroundColor Cyan "Deleting Disk $diskname in $resourcegroup of Location - $location"
Remove-AzDisk -ResourceGroupName $resourcegroup -DiskName $diskname -Force
}
else
{
Write-Host -ForegroundColor Red "Failed to delete the disk $diskname in $resourcegroup of Location - $location since it is Attached"
$tim = (Get-Date).tostring("dd-MM-yyyy-hh:mm:ss")
LogWrite "$tim - Failed to delete the disk $diskname in $resourcegroup of Location - $location since it is Attached"
}
}
#####################################################################
##########################DeleteOrphanedNICs##########################
$csv = Import-Csv "nic.csv" #name of csv file
$filetim = (Get-Date).tostring("dd-MM-yyyy-hh-mm")
$Logfile = "log-$filetim.log"
Function LogWrite
{
Param ([string]$logstring)
Add-content $Logfile -value $logstring
}
Start-Transcript -Path $Logfile
Stop-Transcript
LogWrite " "
LogWrite " "
LogWrite "***** LogFile *****"
LogWrite " "
LogWrite " "
$csv | ForEach-Object {
$nicname = $_.NAME
$resourcegroup = $_.RESOURCEGROUP
$location = $_.LOCATION
$tim = (Get-Date).tostring("dd-MM-yyyy-hh:mm:ss")
LogWrite "$tim - Deleting NIC $nicname in $resourcegroup of Location - $location"
Write-Host -ForegroundColor Cyan "Deleting NIC $nicname in $resourcegroup of Location - $location"
Remove-AzNetworkInterface -Name $nicname -ResourceGroup $resourcegroup -Force
}
#####################################################################
##########################DeleteOrphanedNSGs#########################
$csv = Import-Csv "nsg.csv" #name of csv file
$filetim = (Get-Date).tostring("dd-MM-yyyy-hh-mm")
$Logfile = "log-deleteNSG-$filetim.log"
Function LogWrite
{
Param ([string]$logstring)
Add-content $Logfile -value $logstring
}
Start-Transcript -Path $Logfile
Stop-Transcript
LogWrite " "
LogWrite " "
LogWrite "***** LogFile *****"
LogWrite " "
LogWrite " "
$csv | ForEach-Object {
$nsgname = $_.RESOURCE
$resourcegroup = $_.RESOURCEGROUP
$location = $_.LOCATION
$tim = (Get-Date).tostring("dd-MM-yyyy-hh:mm:ss")
LogWrite "$tim - Deleting NSG $nsgname in $resourcegroup of Location - $location"
Write-Host -ForegroundColor Cyan "Deleting NSG $nsgname in $resourcegroup of Location - $location"
Remove-AzNetworkSecurityGroup -Name $nsgname -ResourceGroup $resourcegroup -Force
}
#####################################################################
##########################DeleteOrphanedPiP##########################
$csv = Import-Csv "pip.csv" #name of csv file
$filetim = (Get-Date).tostring("dd-MM-yyyy-hh-mm")
$Logfile = "log-deletePIP-$filetim.log"
Function LogWrite
{
Param ([string]$logstring)
Add-content $Logfile -value $logstring
}
Start-Transcript -Path $Logfile
Stop-Transcript
LogWrite " "
LogWrite " "
LogWrite "***** LogFile *****"
LogWrite " "
LogWrite " "
$csv | ForEach-Object {
$pipname = $_.NAME
$resourcegroup = $_.RESOURCEGROUP
$location = $_.LOCATION
$tim = (Get-Date).tostring("dd-MM-yyyy-hh:mm:ss")
LogWrite "$tim - Deleting Public IP $pipname in $resourcegroup of Location - $location"
Write-Host -ForegroundColor Cyan "Deleting Public IP $pipname in $resourcegroup of Location - $location"
Remove-AzPublicIpAddress -Name $pipname -ResourceGroupName $resourcegroup -Force
}
#####################################################################
© 2023 Tech Blog
Theme by Anders Noren — Up ↑