Advertisement
Guest User

Untitled

a guest
Oct 27th, 2015
161
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.46 KB | None | 0 0
  1. <#
  2. .SYNOPSIS
  3. Scan an entire subnet for computers doing a reverse lookup, any Windows
  4. machines that are found will get basic information about them. Also
  5. gets basic Active Directory information.
  6. .DESCRIPTION
  7. Meant for the on the go consultant, or anyone who just wants to understand
  8. their network better. Run the script, sit back and wait for the email which
  9. includes HTML reports on all workstations, servers and IP list (only as good
  10. as the reverse DNS).
  11.  
  12. It will also email you the raw data in XML format which can easily be inputed
  13. using Import-CliXml and than manipulated using Powershell.
  14.  
  15. Script will prompt you for password for the mail relay, if you specify a
  16. Mail Authentication User (the -MailAuthUser parameter).
  17.  
  18. ** It is hightly recommended that you edit the PARAM section to match your needs,
  19. especially the To, MailAuthUser, Port and SMTPServer parameters. **
  20.  
  21. For security reasons, you cannot specify the password in the script but must
  22. wait for it to prompt you.
  23.  
  24. .PARAMETER Company
  25. Name of the Company being scanned. This is only used in the header of the
  26. reports.
  27. .PARAMETER Network
  28. IP address of the network you are on. Can be any valid IP address in the range.
  29. I.E. 192.168.0.5. If you do not provide the network information the script
  30. will look at the workstations IP information and use that.
  31. .PARAMETER SubnetMask
  32. Subnet mask of the network you wish to scan. It is also possible to use a
  33. custom subnet mask to only scan a few devices, if you know how to change that.
  34. I.E. 255.255.255.0. If you do not provide the subnet mask information the script
  35. will look at the workstations IP information and use that.
  36. .PARAMETER NoNetwork
  37. Use this switch to instruct the script to NOT scan all IP addresses
  38. .PARAMETER NoADInformation
  39. Use this switch to instruct the script to NOT scan for Active Directory information.
  40. .PARAMETER To
  41. Who you wish the script to email the reports to in standard SMTP format:
  42. me@myself.com
  43. .PARAMETER MailAuthUser
  44. The username for authenticating to the SMTP server. If you leave this blank the
  45. script will not attempt authentication.
  46. .PARAMETER Port
  47. Port used for SMTP relay (587 is the standard SSL relay port)
  48. .PARAMETER SMTPServer
  49. IP address or host name of the SMTP relay server.
  50. .PARAMTER MaxThreads
  51. Maximum number of concurrent threads that can run on the PC. Bigger workstations
  52. can run more threads, but if you run too many threads you can actually slow the
  53. process down.
  54. .INPUTS
  55. None
  56. .OUTPUTS
  57. ND.HTML - HTML report of all workstations and servers as well as a IP listing.
  58. AD.HTML - HTML report of basic Active Directory information.
  59. ND.XML - raw data of all workstations, servers and IP addresses
  60. AD.XML - raw Active Directory information
  61. .EXAMPLE
  62. .\Send-NetworkDiscovery.ps1
  63.  
  64. Accepts all defaults and runs on the local IP subnet. Assuming the network is
  65. 192.168.0.0 with a mask of 255.255.255.0 all IP address from 192.168.0.1 to
  66. 192.168.0.254 will be scanned. Email will be sent to the default user
  67. (you@yourdomain.com) and relayed through GMail using the user account you@gmail.com.
  68. Script will prompt for password.
  69. .EXAMPLE
  70. .\Send-NetworkDiscovery.ps1 -Company "My Company" -To "me@mycompany.com" -MailAuthUser "me@gmail.com"
  71.  
  72. Scan entire network, email to Me@mycompany.com using me@gmail.com to authenticate
  73. against the default smtp.gmail.com server.
  74.  
  75. .EXAMPLE
  76. .\Send-NetworkDiscovery.ps1 -NoAD
  77.  
  78. Only scan the network, do not scan for Active Directory information.
  79. .NOTES
  80. Author: Martin Pugh
  81. Twitter: @thesurlyadm1n
  82. Spiceworks: Martin9700
  83. Blog: www.thesurlyadmin.com
  84.  
  85. Changelog:
  86. 1.0 Initial Release
  87. .LINK
  88. http://community.spiceworks.com/scripts/show/1907-send-networkdiscovery-network-discovery
  89. #>
  90. [CmdletBinding()]
  91. Param (
  92. [Parameter(Mandatory=$true)]
  93. [string]$Company,
  94. [Alias("IP")]
  95. [string]$Network,
  96. [Alias("Mask")]
  97. [string]$SubnetMask,
  98. [switch]$NoNetwork,
  99. [Alias("NoAD")]
  100. [switch]$NoADInformation,
  101. [string]$To = "you@yourdomain.com",
  102. [string]$MailAuthUser = "you@gmail.com",
  103. [int]$Port = 587,
  104. [string]$SMTPServer = "smtp.gmail.com",
  105. [int]$MaxThreads = 10
  106. )
  107.  
  108. If ($MailAuthUser)
  109. { $Password = Read-Host "Password for `"$MailAuthUser`"" -AsSecureString
  110. }
  111.  
  112. #region Functions
  113. #Awesome IP range functions from Chris Dent
  114. #Read his blog: http://www.indented.co.uk/
  115.  
  116. Function Get-NetworkRange {
  117. Param (
  118. [String]$IP,
  119. [String]$Mask
  120. )
  121. If ($IP.Contains("/"))
  122. { $Temp = $IP.Split("/")
  123. $IP = $Temp[0]
  124. $Mask = $Temp[1]
  125. }
  126.  
  127. If (!$Mask.Contains("."))
  128. { $Mask = ConvertTo-Mask $Mask
  129. }
  130.  
  131. $DecimalIP = ConvertTo-DecimalIP $IP
  132. $DecimalMask = ConvertTo-DecimalIP $Mask
  133.  
  134. $Network = $DecimalIP -BAnd $DecimalMask
  135. $Broadcast = $DecimalIP -BOr ((-BNot $DecimalMask) -BAnd [UInt32]::MaxValue)
  136.  
  137. For ($i = $($Network + 1); $i -lt $Broadcast; $i++) {
  138. ConvertTo-DottedDecimalIP $i
  139. }
  140. } #End Get-NetworkRange
  141.  
  142. Function ConvertTo-DottedDecimalIP {
  143. <#
  144. .Synopsis
  145. Returns a dotted decimal IP address from either an unsigned 32-bit integer or a dotted binary string.
  146. .Description
  147. ConvertTo-DottedDecimalIP uses a regular expression match on the input string to convert to an IP address.
  148. .Parameter IPAddress
  149. A string representation of an IP address from either UInt32 or dotted binary.
  150. #>
  151.  
  152. [CmdLetBinding()]
  153. Param(
  154. [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
  155. [String]$IPAddress
  156. )
  157.  
  158. Process {
  159. Switch -RegEx ($IPAddress) {
  160. "([01]{8}\.){3}[01]{8}" {
  161. Return [String]::Join('.', $( $IPAddress.Split('.') | ForEach-Object { [Convert]::ToUInt32($_, 2) } ))
  162. }
  163. "\d" {
  164. $IPAddress = [UInt32]$IPAddress
  165. $DottedIP = $( For ($i = 3; $i -gt -1; $i--) {
  166. $Remainder = $IPAddress % [Math]::Pow(256, $i)
  167. ($IPAddress - $Remainder) / [Math]::Pow(256, $i)
  168. $IPAddress = $Remainder
  169. } )
  170.  
  171. Return [String]::Join('.', $DottedIP)
  172. }
  173. default {
  174. Write-Error "Cannot convert this format"
  175. }
  176. }
  177. }
  178. } #End ConvertTo-DottedDecimalIP
  179.  
  180. Function ConvertTo-DecimalIP {
  181. <#
  182. .Synopsis
  183. Converts a Decimal IP address into a 32-bit unsigned integer.
  184. .Description
  185. ConvertTo-DecimalIP takes a decimal IP, uses a shift-like operation on each octet and returns a single UInt32 value.
  186. .Parameter IPAddress
  187. An IP Address to convert.
  188. #>
  189.  
  190. [CmdLetBinding()]
  191. Param(
  192. [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
  193. [Net.IPAddress]$IPAddress
  194. )
  195.  
  196. Process {
  197. $i = 3; $DecimalIP = 0;
  198. $IPAddress.GetAddressBytes() | ForEach-Object { $DecimalIP += $_ * [Math]::Pow(256, $i); $i-- }
  199.  
  200. Return [UInt32]$DecimalIP
  201. }
  202. } #End ConvertTo-DecimalIP
  203.  
  204.  
  205. #Set Alternating Rows in HTML tables
  206. Function Set-AlternatingRows {
  207. [CmdletBinding()]
  208. Param(
  209. [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
  210. [object[]]$HTMLDocument,
  211.  
  212. [Parameter(Mandatory=$True)]
  213. [string]$CSSEvenClass,
  214.  
  215. [Parameter(Mandatory=$True)]
  216. [string]$CSSOddClass
  217. )
  218. Begin {
  219. $ClassName = $CSSEvenClass
  220. }
  221. Process {
  222. [string]$Line = $HTMLDocument
  223. If ($Line.Contains("<tr>"))
  224. {
  225. $Line = $Line.Replace("<tr>","<tr class=""$ClassName"">")
  226. If ($ClassName -eq $CSSEvenClass)
  227. { $ClassName = $CSSOddClass
  228. }
  229. Else
  230. { $ClassName = $CSSEvenClass
  231. }
  232. }
  233. $Line = $Line.Replace("[return]","<br>")
  234. Return $Line
  235. }
  236. } #End Set-AlternatingRows
  237.  
  238.  
  239. Function ConvertTo-OrderedList
  240. { Param (
  241. [array]$List
  242. )
  243.  
  244. $Fragment = "<ul>"
  245. ForEach ($Line in $List)
  246. { $Fragment += "<li>$Line</li>`n"
  247. }
  248. $Fragment += "</ul>"
  249. Return $Fragment
  250. } #End ConvertTo-OrderedList
  251.  
  252.  
  253. Function ConvertTo-ArrayToString
  254. { Param (
  255. [String[]]$Array
  256. )
  257. For ($i = 0 ; $i -le $Array.Count - 1; $i ++ ) {
  258. If ($i -eq 0)
  259. { [string]$Result = "$($Array[0])"
  260. }
  261. Else
  262. { $Result += "[return]$($Array[$i])"
  263. }
  264. }
  265. Return $Result
  266. } #End ConvertTo-ArrayToString
  267.  
  268. #endregion Functions
  269.  
  270. #Region GetComputerInfo Scriptblock
  271. $GetComputerInfo = {
  272. Param (
  273. [string]$IP
  274. )
  275.  
  276. Function PrepSize
  277. { Param (
  278. [double]$Size
  279. )
  280. If ($Size -ge 1000000000)
  281. { $ReturnSize = "{0:N2} GB" -f ($Size / 1GB)
  282. }
  283. Else
  284. { $ReturnSize = "{0:N2} MB" -f ($Size / 1MB)
  285. }
  286. Return $ReturnSize
  287. }
  288.  
  289. #Reverse Ping to get DNS name (if exists)
  290. $Ping = Get-WMIObject Win32_PingStatus -Filter "Address = '$IP' AND ResolveAddressNames = TRUE"
  291. If ($Ping.StatusCode -eq 0)
  292. { $ComputerName = $Ping.ProtocolAddressResolved
  293. $WMI = Get-WmiObject Win32_OperatingSystem -ComputerName $ComputerName
  294. If ($WMI)
  295. { #Get OS Version
  296. $OS = $WMI.Caption
  297. $OSVersion = $WMI.Version
  298. $ServicePack = "$($WMI.ServicePackMajorVersion).$($WMI.ServicePackMinorVersion)"
  299.  
  300. #Get CPU Information
  301. $WMI = Get-WmiObject Win32_Processor -ComputerName $ComputerName
  302. $NumCPUS = @($WMI).Count
  303. $CPU = @($WMI)[0].Name
  304.  
  305. #Get Chassis Information
  306. $WMI = Get-WmiObject Win32_BaseBoard -ComputerName $ComputerName
  307. $MakeModel = "$($WMI.Manufacturer)/$($WMI.Model)"
  308. $ServiceTag = $WMI.Product
  309. $SerialNumber = $WMI.SerialNumber
  310.  
  311. #Get Memory Information
  312. $WMI = Get-WmiObject Win32_PhysicalMemory -ComputerName $ComputerName
  313. $Memory = PrepSize ( ($WMI | Measure-Object -Property Capacity -Sum).Sum )
  314.  
  315. #Get Logical Disk Information
  316. $WMI = @(Get-WmiObject Win32_LogicalDisk -Filter "DriveType = 3" -ComputerName $ComputerName)
  317. $Drives = @()
  318. ForEach ($Drive in $WMI)
  319. { $Capacity = PrepSize ($Drive.Size)
  320. $FreeSpace = PrepSize ($Drive.FreeSpace)
  321. $Drives += "$($Drive.DeviceID) $Capacity ($FreeSpace Free)"
  322. }
  323. $Status = "Successful"
  324. }
  325. Else
  326. { If ($ComputerName -eq $IP)
  327. { $ComputerName = "Unknown"
  328. }
  329. $Status = "Ping successful, no WMI response"
  330. }
  331. }
  332. Else
  333. { $ComputerName = "None"
  334. $Status = "No Response to Ping"
  335. }
  336. New-Object PSObject -Property @{
  337. 'Computer Name' = $ComputerName
  338. Status = $Status
  339. IP = $IP
  340. OS = $OS
  341. 'OS Version' = $OSVersion
  342. 'OS Service Pack' = $ServicePack
  343. CPU = $CPU
  344. 'Number of CPUs' = $NumCPUS
  345. 'Make/Model' = $MakeModel
  346. 'Service Tag' = $ServiceTag
  347. 'Serial Number' = $SerialNumber
  348. Memory = $Memory
  349. 'Hard Drives' = $Drives
  350. }
  351. }
  352. #EndRegion
  353.  
  354. cls
  355. $Attachments = @()
  356. $MyPath = Split-Path $MyInvocation.MyCommand.Path
  357. If (-not $NoNetwork)
  358. { $HTMLCSS = @"
  359. <style>
  360. TABLE {border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}
  361. TH {border-width: 1px;padding: 3px;border-style: solid;border-color: black;background-color: #6495ED;}
  362. TD {border-width: 1px;padding: 3px;border-style: solid;border-color: black;}
  363. .odd { background-color:#ffffff; }
  364. .even { background-color:#dddddd; }
  365. </style>
  366. <title>
  367. Network Information for $Company
  368. </title>
  369. "@
  370.  
  371. #Clean out any old jobs, if any
  372. ForEach ($Job in (Get-Job))
  373. { Remove-Job $Job
  374. }
  375.  
  376. #Get IP Address and Subnetmask if not specified in Parameters
  377. $IPInfo = @(Get-WMIObject -Class Win32_NetworkAdapterConfiguration -Filter "IPEnabled='TRUE'")
  378. If (-not $Network)
  379. { $Network = $IPInfo[0].IPAddress
  380. }
  381. If (-not $SubnetMask)
  382. { $SubnetMask = $IPInfo[0].IPSubnet
  383. }
  384.  
  385. #Calculate IP range and submit GetComputerInfo scriptblock as background jobs
  386. $Range = Get-NetworkRange $Network $SubnetMask
  387. $Submitted = 0
  388. ForEach ($IPAddress in $Range)
  389. { $JobCount = @(Get-Job -State Completed).Count
  390. Write-Progress -Id 1 -Activity "Gathering Computer Information..." -Status "Submitting threads: $($Range.Count - $Submitted)" -PercentComplete ($JobCount / $Range.Count * 100)
  391. While (@(Get-Job | Where { $_.State -ne "Completed" }).Count -ge $MaxThreads) {
  392. Write-Verbose "Waiting for open thread...($MaxThreads Maximum)"
  393. Start-Sleep -Seconds 3
  394. }
  395. $Job = Start-Job -ScriptBlock $GetComputerInfo -ArgumentList $IPAddress
  396. $Submitted ++
  397. Write-Verbose ($Job | Select Id,Name,State,HasMoreData)
  398. }
  399.  
  400. #Wait for all jobs to complete
  401. Do {
  402. $JobCount = @(Get-Job -State Completed).Count
  403. Write-Progress -Id 1 -Activity "Gathering Computer Information..." -Status "Waiting for background jobs to finish: $($Range.Count - $JobCount)" -PercentComplete ($JobCount / $Range.Count * 100)
  404. Write-Verbose "Waiting for background jobs..."
  405. Start-Sleep -Seconds 3
  406. } While (@(Get-Job | Where { $_.State -ne "Completed" }).Count -ne 0)
  407. Write-Progress -Id 1 -Activity "Gathering Computer Information..." -Status "Background Jobs Completed" -PercentComplete 100
  408. Write-Verbose "All jobs completed!"
  409.  
  410. #Now retrieve information from the jobs
  411. $Data = @()
  412. $Data = ForEach ($Job in (Get-Job))
  413. { Receive-Job $Job
  414. Remove-Job $Job | Out-Null
  415. }
  416. $WSFragment = $Data | Where { $_.OS -notlike "*Server*" -and $_.OS -ne $null } | Select 'Computer Name',OS,'OS Service Pack','Make/Model','Serial Number','Service Tag',IP,'Number of CPUs',Memory,
  417. @{Label="Hard Drives";Expression={ConvertTo-ArrayToString $_.'Hard Drives'}} | ConvertTo-Html -Fragment | Set-AlternatingRows -CSSEvenClass even -CSSOddClass odd
  418. $IPFragment = $Data | Select IP,'Computer Name' | ConvertTo-Html -Fragment | Set-AlternatingRows -CSSEvenClass even -CSSOddClass odd
  419. $HTML = $Data | Where { $_.OS -like "*Server*" } | Select 'Computer Name',OS,'OS Service Pack','Make/Model','Serial Number','Service Tag',IP,'Number of CPUs',Memory,
  420. @{Label="Hard Drives";Expression={ConvertTo-ArrayToString $_.'Hard Drives'}}
  421. $HTML = $HTML | ConvertTo-Html -Head $HTMLCSS -PreContent "<h2>Network Discovery for $Company</h2><br><h3>Server List</h3>" -PostContent "<br><h3>Workstation List</h3><br>$WSFragment<br><h3>IP List</h3><br>$IPFragment"
  422. $HTML | Set-AlternatingRows -CSSEvenClass even -CSSOddClass odd | Out-File "$MyPath\nd.html"
  423. $Data | Export-Clixml "$MyPath\Data.xml"
  424. $Attachments += "$MyPath\nd.html","$MyPath\Data.xml"
  425. }
  426.  
  427. If (-not $NoADInformation)
  428. { #Get AD Information
  429. $WSHNetwork = New-Object -ComObject "Wscript.Network"
  430. $Domain = $WSHNetwork.UserDomain
  431. $Root = [ADSI] "LDAP://RootDSE"
  432. $Config = $Root.ConfigurationNamingContext
  433.  
  434. #Get Sites
  435. $SitesDN = "LDAP://CN=Sites,$Config"
  436. $Sites = ForEach ($Site in ($([ADSI]$sitesDN).PSBase.Children | Where { $_.objectClass -eq "site" }))
  437. { $Site.Name
  438. }
  439.  
  440. #Get Domain Controllers and Global Catalogs
  441. $DomainControllers = @()
  442. $DomainControllersSites = @()
  443. $GC = @()
  444. $DCs = ([System.DirectoryServices.ActiveDirectory.DomainController]::FindAll((New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$Domain))))
  445. ForEach ($DC in $DCs)
  446. { ForEach ($Role in $DC.Roles)
  447. { Switch ($Role)
  448. { "SchemaRole" {$FSMOSchema = $DC.Name}
  449. "NamingRole" {$FSMONaming = $DC.Name}
  450. "PdcRole" {$FSMOPDC = $DC.Name}
  451. "RidRole" {$FSMORID = $DC.Name}
  452. "InfrastructureRole" {$FSMOInfrastructure = $DC.Name}
  453. }
  454. }
  455. $DomainControllers += $DC.Name
  456. $DomainControllersSites += $DC.SiteName
  457. If ($DC.IsGlobalCatalog())
  458. { $GC += $DC.Name
  459. }
  460. }
  461. $ActiveDirectory = New-Object PSObject -Property @{
  462. Domain = $Domain
  463. Sites = $Sites
  464. 'Domain Controllers' = $DomainControllers
  465. 'DC Site' = $DomainControllersSites
  466. 'Global Catalogs' = $GC
  467. 'Forest Schema Master' = $FSMOSchema
  468. 'Forest Naming Master' = $FSMONaming
  469. 'Domain PDC Emulator' = $FSMOPDC
  470. 'Domain RID Master' = $FSMORID
  471. 'Domain Infrastructure Master' = $FSMOInfrastructure
  472. }
  473. $DCs = @()
  474. For ($i = 0; $i -lt $DomainControllers.Count; $i ++ )
  475. { $DCs += New-Object PSObject -Property @{
  476. Name = $DomainControllers[$i]
  477. Site = $DomainControllersSites[$i]
  478. 'IP Address' = (Test-Connection $DomainControllers[$i] -Count 1).IPv4Address.IPAddressToString
  479. }
  480. }
  481. #Build the HTML
  482. $SiteFragment = ConvertTo-OrderedList ($Sites | Sort)
  483. $DCFragment = $DCs | Select Name,Site,'IP Address' | Sort Site,Name | ConvertTo-Html -Fragment | Set-AlternatingRows -CSSEvenClass even -CSSOddClass odd
  484. $GCFragment = ConvertTo-OrderedList ($GC | Sort)
  485.  
  486. $Body = @"
  487. <html>
  488. <head>
  489. <style type='text/css'>
  490. body {background-color:#DCDCDC;font-size:20px;}
  491. b {font-size:24px;}
  492. TABLE {border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;font-size:20px;}
  493. TH {border-width: 1px;padding: 3px;border-style: solid;border-color: black;background-color: #6495ED;width:300px;}
  494. TD {border-width: 1px;padding: 3px;border-style: solid;border-color: black;}
  495. </style>
  496. <title>
  497. Active Directory Information for $Company
  498. </title>
  499. </head>
  500. <body>
  501. <h2>Active Directory Information for $Company</h2>
  502. <br>
  503. <b>Domain Name:</b> $domain<br>
  504. <br>
  505. <p><b>AD Sites</b>
  506. $SiteFragment
  507. <br>
  508. <b>Domain Controllers</b><p>
  509. $DCFragment
  510. <br>
  511. <br>
  512. <b>FSMO Role Holders</b><p>
  513. <table>
  514. <th>Role</th><th>Holder</th>
  515. <tr><td>Forest-wide Schema Master</td><td>$FSMOSchema</td>
  516. <tr><td>Forest-wide Domain Naming Master</td><td>$FSMONaming</td>
  517. <tr><td>Domain's PDC Emulator</td><td>$FSMOPDC</td>
  518. <tr><td>Domain's RID Master</td><td>$FSMORID</td>
  519. <tr><td>Domain's Infrastructure Master</td><td>$FSMOInfrastructure</td>
  520. </table>
  521. <br>
  522. <br>
  523. <b>Global Catalogs</b>
  524. $GCFragment
  525. </body>
  526. </html>
  527. "@
  528. $Body | Out-File "$MyPath\ad.html"
  529. $ActiveDirectory | Export-Clixml "$MyPath\AD.xml"
  530. $Attachments += "$MyPath\ad.html","$MyPath\AD.xml"
  531. }
  532.  
  533. #Send the results
  534. $Email = New-Object System.Net.Mail.MailMessage
  535. $Email.To.Add($To)
  536. $Email.From = "martin9700@thesurlyadmin.com"
  537. $Email.Subject = "Network Discovery for $Company"
  538. $Email.Body = "Network discovery completed."
  539. ForEach ($Attachment in $Attachments)
  540. { $Att = New-Object System.Net.Mail.Attachment $Attachment
  541. $Email.Attachments.Add($Att)
  542. }
  543. $SMTPClient = New-Object System.Net.Mail.SmtpClient($SMTPServer)
  544. If ($MailAuthUser)
  545. { $SMTPClient.Port = $Port
  546. $SMTPClient.Credentials = New-Object System.Net.NetworkCredential($MailAuthUser,$Password)
  547. $SMTPClient.EnableSSL = $true
  548. }
  549. $SMTPClient.Send($Email)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement