Showing posts with label automation. Show all posts
Showing posts with label automation. Show all posts

Thursday, 12 August 2021

Microsoft Teams - Copy Users Team Memberships to Another User

Microsoft Teams doesn't have any easy facility to export all the teams a particular user is a member of which can be useful if you want to copy the team memberships from one user to another.

Thankfully there is a way to do it though - since Microsoft Teams are actually O365 groups behind the scenes, we can utilise some O365 powershell cmdlet magic to accomplish this.

First, we need to connect to our O365 environment then import the session so we can access the cmdlets

$ex = New-PSSession -ConfigurationName Microsoft.Exchange -Credential user@domain.com -ConnectionUri https://outlook.office365.com/powershell -Authentication basic -AllowRedirection

import-pssession $ex

Next, let's get a list of all groups of the user we want to copy from - ie. the source user (be sure to adjust the source user variable to use your own target users email address)

$sourceuser = "user1@domain.com"

$Office365GroupsMember = Get-UnifiedGroup | where { (Get-UnifiedGroupLinks $_.Alias -LinkType Members | foreach {$_.primarysmtpaddress}) -contains $sourceuser}


You can then look at the $Office365GroupsMember variable to see a list of all the O365 groups that the source user is a member of

If you have a mixture of O365 groups AND MSTeams and want to isolate the MS Teams - you can filter the results further by using the command beloww

$o365teamsgroups = $office365GroupsMember | where {$_.serviceendpointuris -like "*MicrosoftTeams*"}

Now that we have our list of Teams, we can go through the list and add our target user to each team. But to do this we need the unique Group ID for each team which is included in the script below. Be sure to update the $targetuser variable with the email address of the user you wish to add to the groups.

Connect-MicrosoftTeams
$targetuser = "user2@domain.com"

foreach ($o365team in $o365teamgroups)
    {
    $teamprops = get-team -displayname $($o365team.displayname)
    write-host "Adding $targetuser to $($teamprops.displayname) team"
    add-teamuser -groupid $($teamprops) -user $targetuser
    }

Monday, 17 October 2016

Shutdown/Restart Mac from Windows - Command Line

Administrators may occasionally be required to perform maintenance on Mac devices/servers from a windows based operating system. The following utility and command can be used to remotely shutdown/restart/reboot a mac/linux device from the command line on a windows based system - eg. Windows 7, 8, 10, Server 2003, 2008, 2012 etc.

First of all, create a .txt file (in this case it will be placed in C:\ but it can be placed anywhere), and put the following line inside the text file. This is the actual shutdown command that will be sent to the Mac/Linux device

sudo shutdown -r now

Next we need to download the application plink.exe. Plink.exe is a free utility that can be download absolutely free from the website below;

http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html

Place the plink.exe file into a folder on the windows device, and then run the following command from a command line. Note that this command references the command.txt we created above, so adjust the path accordingly if you didn't place it in C:\ as per this example

plink.exe -ssh -t mac-ipaddress -l root -pw macrootpw -m c:\command.txt

mac-ipaddress = the IP address or hostname of the Mac/Linux device to be shutdown
macrootpw = the password for the "root" user account of the Mac/Linux device to be shutdown

If you do not have the "root" user account enabled, or do not know the password it can be enabled and reset by following the instructions in the following link;

https://support.apple.com/en-au/HT204012


Friday, 14 October 2016

Windows Server Health Check Script - Powershell

Here is a script that I have developed to run a "health check" on a windows server. The script can be run on demand, but I tend to run them from Windows Task Scheduler with a trigger to run on windows startup to automatically check some of the key things within a windows server. At a high level, here is what is contained within the script;
  • A report of all services with a start mode of "Automatic" but are not in a "running" state
  • Any events in the windows Application log from the past 24 hours that are not categorized as "informational"
  • Any events in the windows System log from the past 24 hours that are not categorized as "informational"
  • A size report of all logical disks in the server showing size, and free space in GB and as a percentage
  • Average CPU load on server
  • Current memory usage (as a percentage)
All this information is retrieved by the script and stored in a .html file. At the end of the script the .html file is saved to disk and sent as an email attachment.

A few other points to note;
  • The start-sleep command is used at the start of the script to delay the script running for 300 seconds (5 minutes). If the script runs as soon as it is triggered on windows startup, not all windows components are ready/loaded and the results of the script are incomplete
  • The log file is saved by default into C:\Admin\ServerHealthChecks with a filename based on the target IP/hostname and date/time the script runs. This can be changed using the $logfile variable. Make sure the path specified in $logfile exists otherwise the .html file will not save correctly
  • If a disk has 10-20% free space, it's text will be orange in colour. Less than 10% will be red. Otherwise it will be green
  • Likewise, if CPU usage is 80-90% it will be orange. 90-100% will be red
  • If free memory is 10-20%, it will be orange. Less than 10% will be red
  • Be sure to update the variables at the end of the script (under #Send Log File via Email) to work with your email system/environment
Powershell script/code is below;

#Server Health Checker
#Author: Peter Morrissey

start-sleep 300

$target = "127.0.0.1"

#Get Last Bootup Time
$osprops = get-wmiobject -class win32_operatingsystem 
$lastboot = $osprops.ConvertToDateTime($osprops.LastBootUpTime)

#Log File
$error.clear()
$fulldate = Get-Date
$logfile = "c:\" + "Admin\" + "ServerHealthChecks\" + $FullDate.ToString("yyyyMMddHHmm") + "-" + "$Target" + ".html"
$global:strname = $env:username
$global:html = @()
$html += "<html>"
$html += '<font face="courier">'
$html += "<title>Server Health Check - $Target</title>"
$html += "<h3>$FullDate</h3>"
$html += "<h4>Server Health Check Script - Run by $global:strname</h4>"
$html += "<h4>Target Server: $Target</h4>"
$html += "<h4>Hostname: $env:computername</h4>"
$html += "<h4>Last Boot: $lastboot </h4>"
$html += "<p>"
$html += "******************************************************************************************* <p>"

#Services Check
write-host "Getting Service Information"
$html += '<u>Service Report - Services with StartMode "Auto" and State not currently "Running" </u><p>'

$servicelist = invoke-command -computername $target {
        get-wmiobject -class Win32_Service | Where {$_.StartMode -eq "Auto" -and $_.State -ne "Running"} | Select DisplayName, Name, StartMode, State
        }
if ($servicelist){$temphtml = $servicelist | Select DisplayName, Name, StartMode, State | ConvertTo-HTML -fragment
                    ForEach ($line in $temphtml){$html += "$Line"} }
ELSE {write-host "All automatic services are running";$html += "All automatic services are running <p>"}

$html += "<p> ******************************************************************************************* <p>"

#EventLog Checks
write-host "Querying Application Log on $Target"
$appeventlog = invoke-command -computername $target {
        $targetDate = Get-Date
        $targetdate = $targetdate.adddays(-1)
        get-eventlog -logname "Application" -after $targetdate | where-object {$_.entrytype -ne "Information" -and $_.Source -ne "Print" -and $_.Source -ne "TermServDevices"} 
        }

if ($appeventlog){#write-host $AppEventLog
                  $html += "<u>Application Log Non-Information Events (Last 24 Hours)</u><p>" 
                  $temphtml = $appeventlog |  Select TimeGenerated, EntryType, Source, Message | ConvertTo-HTML -fragment
                  foreach ($line in $temphtml){$html += "$line"}
                  }
                  
ELSE {$html += "No non-informational events found in application log in past 24 hours <p>"}

$html += "<p>*******************************************************************************************<p>"

write-host "Querying System Log on $Target"

$syseventlog = invoke-command -computername $Target {
        $targetdate = get-date
        $targetdate = $targetdate.adddays(-1)
        get-eventlog -logname "System" -after $targetdate | where-object {$_.entrytype -ne "Information" -and $_.source -ne "Print" -and $_.source -ne "TermServDevices"} 
        }

if ($syseventlog){#write-host $syseventlog
                  $html += "<u>System Log Non-Information Events (Last 24 Hours)</u><p>"
                  $temphtml = $syseventlog | Select TimeGenerated, EntryType, Source, Message | ConvertTo-HTML -fragment
                  ForEach ($line in $temphtml){$html += "$Line"}
                  }
                  
else {$html += "No non-informational events found in system log in past 24 hours<p>"}

$html += "<p>*******************************************************************************************<p>"

#Local Disk Health Check
write-host "Getting logical disk information"
$diskreport = invoke-command -computername $target {
    Get-WmiObject Win32_logicaldisk | Select DeviceID, MediaType, VolumeName, `
    @{Name="Size(GB)";Expression={[decimal]("{0:N0}" -f($_.size/1gb))}}, `
    @{Name="Free Space(GB)";Expression={[decimal]("{0:N0}" -f($_.freespace/1gb))}}, `
    @{Name="Free (%)";Expression={"{0,6:P0}" -f(($_.freespace/1gb) / ($_.size/1gb))}} `
    }

$html += "<u>Logical Disk Report</u><p>"
$temphtml = $DiskReport | Select DeviceID, VolumeName, "Size(GB)", "Free Space(GB)", "Free (%)" | ConvertTo-HTML -fragment
foreach ($Line in $temphtml)
    {
    if ($line -like "*%<*")
        {
        $lineindex = [array]::IndexOf($temphtml, $line)
        $templine = $line -split "%"
        $templine = $templine -replace(" ","")
        $templine = $templine[0] -split "<td>"
        $templine = $templine[5]
        $templine = $templine.trim()
        $templine = [decimal]$templine
               
        if ($templine -le 20 -and $templine -ge 10){$temphtml[$lineindex] = $temphtml[$lineindex].Replace("<td>",'<td><font color="orange">')}
        if ($templine -lt 10 ){$temphtml[$lineindex] = $temphtml[$lineindex].Replace("<td>",'<td><font color="red">');
                                $smtpclient = new-object system.net.mail.smtpClient 
                                $mailmessage = New-Object system.net.mail.mailmessage 
                                $smtpclient.Host = "192.168.0.5" 
                                $mailmessage.from = ("alerts@mitx.dynu.com") 
                                $mailmessage.To.add("peter@mitx.dynu.com")
                                $mailmessage.Subject = “Server Health Check Alert - Disk Space Low - $target"
                                $mailmessage.Body = "Low disk space found on $target -  $temphtml"
                                $mailmessage.IsBodyHtml = $true
                                $smtpclient.Send($mailmessage)
                                $mailmessage.dispose()
                              }
        }
    }

foreach ($line in $temphtml){$html += "$line"}

$html += "<p>*******************************************************************************************<p>"

#Check CPU Load
write-host "Getting CPU Stats"
$cpudata = get-wmiobject win32_processor -computername $target | measure-object -property LoadPercentage -average | select Average
$html += "<u>CPU Load (Average)</u><p>"
$cpuusage = $($cpudata.average)
    if ($cpuusage -ge 80 -and $cpuusage -le 90){$html += '<font color="orange">' + "Average CPU Load: $cpuusage%" + '</font>'}
    elseif ($cpuusage -gt 90){$html += '<font color="red">' + "Average CPU Load: $cpuusage%" + '</font>'}
    elseif ($cpuusage -lt 80){$html += '<font color = "green">' + "Average CPU Load: $cpuusage%" + '</font>'}
$html += "<p>*******************************************************************************************<p>"

#Check Memory Usage
write-host "Getting Memory Usage"
$html += "<u>Memory Usage Report</u><p>"
$memdata = get-wmiobject win32_operatingsystem -computername $target | select FreePhysicalMemory, FreeVirtualMemory, TotalVirtualMemorySize, TotalVisibleMemorySize
$freephysicalmem = $($memdata.freephysicalmemory)
$freevirtualmem = $($memdata.freevirtualmemory)
$totalvirtualmem = $($memdata.totalvirtualmemorysize)
$totalvisiblemem = $($memdata.totalvisiblememorysize)
$memusage = ($totalvisiblemem - $freephysicalmem) / $totalvisiblemem * 100
$freemem = $freephysicalmem / $totalvisiblemem * 100
[decimal]$freemem = "{0:N0}" -f $freemem

if ($freemem -lt 10){$html += '<font color="red">' + "Free Memory (%): $freemem" + '</font>'}
elseif ($freemem -gt 10 -and $freemem -le 20){$html += '<font color="orange">' + "Free Memory (%): $freemem" + '</font>'}
elseif ($freemem -gt 20){$html += '<font color="green">' + "Free Memory (%): $freemem" + '</font>'}
$html += "<p>*******************************************************************************************<p>"

#Export Log File
$html | out-file $LogFile

#Open Log File in Internet Explorer
#Invoke-Item $Logfile

#Send Log File via Email
$smtpclient = new-object system.net.mail.smtpClient 
$mailmessage = New-Object system.net.mail.mailmessage 
$smtpclient.Host = "mail.server.com" 
$mailmessage.from = ("user@domain.com") 
$mailmessage.To.add("user@domain.com")
$mailmessage.Subject = “Server Health Check Results - $target"
$mailmessage.Body = "Server Health Check Results for $target"
$mailmessage.Attachments.Add($LogFile)
$mailmessage.IsBodyHtml = $true
$smtpclient.Send($mailmessage)
$mailmessage.dispose()

Thursday, 13 October 2016

Automatically logoff sessions from windows server using Powershell

Windows server has the functionality through group policy to automatically log off users at a certain time of day, or once their set logon hours expire etc, but in my experience (and many other users based on internet research), it doesn't work very reliably.

Here is a script you  can use that will get a list of all current user sessions on a server, and log them off. A "safelist" is also included where you can specify usernames that should not be logged off automatically by this script - ie. administrator accounts etc. Usernames should be specified in inverted commas and separated by single commas.

I've also incorporated logging functionality, as it may be useful to know what users are staying logged onto the server (perhaps when they shouldn't be), and to be 100% certain about what the script is doing, or has done. Adjust the $logfile variable as required, or ensure the default folder (C:\Admin) exists for it to work correctly.

This does not require any additional modules to be installed either.

The script works by using the query session command, and then manipulating/formatting the results to obtain a list of current user sessions. Because the query session command is a DOS based command, the results aren't formatted nicely into variables/members that powershell can easily understand and work with, so formatting/manipulation is done using the .Substring and .Trim functions. The list of user sessions t hat is obtained is then compared against the safelist and if the user is not present in the safelist, is then logged off the server.

You will need to setup a scheduled task to run this powershell script - you can view my blog post here on setting up Powershell scripts to run via scheduled tasks in windows

$safelist = "administrator", "user1"
$date = get-date -f "ddMMyyyy"
$logfile = "C:\Admin\LogOffScript-$date.txt"

$sessions = query session |  where-object { $_ -notmatch '^ SESSIONNAME' } | %{
    $item = "" | Select "Active", "SessionName", "Username", "Id", "State", "Type", "Device"
    $item.Active = $_.Substring(0,1) -match '>'
    $item.SessionName = $_.Substring(1,18).Trim()
    $item.Username = $_.Substring(19,20).Trim()
    $item.Id = $_.Substring(39,9).Trim()
    $item.State = $_.Substring(48,8).Trim()
    $item.Type = $_.Substring(56,12).Trim()
    $item.Device = $_.Substring(68).Trim()
    $item
} 

foreach ($session in $sessions)
{
if ($safelist -notcontains $($session.username))
{
$time = get-date
logoff $($session.id)
write-output "$time | Logged off $($session.username)" | out-file $logfile -append
}
}

Wednesday, 5 October 2016

Get Computer Serial Number & HP Product Number using Powershell

You can use powershell to quickly obtain the serial number/service tag for a computer/server

get-wmiobject win32_bios | select SerialNumber



Some computer manufacturers (such as HP) also have a product number that you can access with the following command;

get-wmiobject win32_computersystem | select-object OEMStringArray

This win32_computersystem class also contains other details/information such as the Manufacturer and Model number of the computer system - you can view all the details by removing the pipe and everything after it

get-wmiobject win32_computersystem

This information is also available through the registry via the key
HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System

Tuesday, 4 October 2016

Find and Change Read Only Files using Powershell

This script can be used to search for all files that are marked as "read only" on a windows file system. The script will search all files within the $targetdir variable (including all subfolders)

$targetdir = "D:\multimedia"

$readonlyfiles = get-childitem $targetdir -recurse | where {$_.isreadonly -eq $true}

If you wish to remove the read only property from any read only files that are found, we can adjust the script as per below;

$readonlyfiles = get-childitem $targetdir -recurse | foreach {if($_.isreadonly){$_.isreadonly = $false; write-host $_.fullname}}


The script will also display/echo back any files that it finds and changes from being read-only.

Friday, 30 September 2016

Get Last Windows Operating System Boot Time using Powershell

We can easily use powershell to query a computer to retrieve the time and date that windows last booted/started up.

We run a get-wmiobject query to retrieve the properties from the win32_operatingsystem class and store the results into the $osprops variable. We then run to ConvertToDateTime method on the LastBootUpTime property to return the result and store it in the $lastboot variable

$osprops = get-wmiobject -class win32_operatingsystem 
$lastboot = $osprops.ConvertToDateTime($osprops.LastBootUpTime)

The $lastboot variable will then provide an output like below;



This variable can be parsed through different methods to change the output to a different format. you can view all the different methods available by running $lastboot | get-member

Some examples.

$lastboot.ToShortDateString() will return the above date in the format of mm/dd/yyyy
$lastboot.DayOfWeek will only return the day of the week (eg. Monday, Tuesday, etc)

Wednesday, 28 September 2016

Querying/Checking Windows Logical Disks using Powershell

Below is a script you can use to check/query windows logical disks using powershell. This can be useful to regularly check the free space on logical drives on a windows server or PC.

$diskreport = Get-WmiObject Win32_logicaldisk | Select DeviceID, MediaType, VolumeName, `
    @{Name="Size(GB)";Expression={[decimal]("{0:N0}" -f($_.size/1gb))}}, `
    @{Name="Free Space(GB)";Expression={[decimal]("{0:N0}" -f($_.freespace/1gb))}}, `
    @{Name="Free (%)";Expression={"{0,6:P0}" -f(($_.freespace/1gb) / ($_.size/1gb))}}

The script uses the get-wmiobject cmdlet to query the logical disks configured within windows. The results are then compiled into the $diskreport variable so they can then be further queried or exported to a .csv file.

Free space as a percentage is also calculated automatically as part of this script.

A sample output from the $diskreport variable is below (A:\ and D:\ are the floppy drive and DVD drive with no media loaded which is why they've returned values of 0)

Tuesday, 27 September 2016

Querying/Checking Windows Event Viewer Logs with Powershell

Powershell has a cmdlet you can use to check/query the windows event log.  You can customize this cmdlet in several ways to refine the information that is returned.

For example, you can use the -after switch to only return event log entries after a certain date - such as events only returned within the past 1 day (24 hours)

You can also filter the type of entries that are returned. An example would be to only return warning or error entries, and ignore any "informational" type entries

Let's start with the basic command;

get-eventlog -logname "Application"

This command will return all entries within the Application log, which will usually be alot, and not very useful. You can also use "System" or "Security" as other default logs.

Let's refine the command further, to only return entries for the past day. To do this we will define the current date into the $date variable, and subtract one day from that value

$date = get-date
$date = $date.adddays(-1)

We can now use this $date variable to return all event log entries AFTER this date (using the -after switch)

get-eventlog -logname "Application" -after $date

The script will now return ALL events from the Application log from the past 24 hours. Let's refine it a bit further and filter the results so only Warning and Errors are returned

get-eventlog -logname "Application" -after $date | where-object {$_.entrytype -ne "Information"}

Of course you can store the results into a variable so it can be further queried using subsequent commands;

$events = get-eventlog -logname "Application" -after $date | where-object {$_.entrytype -ne "Information"}

Monday, 19 September 2016

Run/Trigger Powershell Script from Windows Task Scheduler

Windows built in Task Scheduler application can be used to trigger/run powershell scripts on custom schedules as required. Here's how to set it up.


  1. Open Task Scheduler (Control Panel > Administrative Tools > Task Scheduler)
  2. Right click on Task Scheduler Library and select Create Task

  3. Enter a name for the scheduled task. Select the option to Run whether user is logged on or not to ensure the task will still run even if you are not currently logged into the computer

  4. Go to the Triggers tab and click the New button. Set the task to run on the schedule as required (eg. daily, weekly, hourly, etc). I recommend setting the option to Stop the task if it runs longer than and set to 30 mins. This means if there is a bug/problem with the script it will terminate after this time and won't continue running in the background
  5. Select the Actions tab and click the New button. Configure as per below:

    Action: Start a program
    Program/script: C:\Windows\System32\WindowsPowershell\v1.0\powershell.exe
    Add Arguments: (Full path to your powershell script .ps1 file)

    Example path may be D:\Powershell\TestScript.ps1

  6. Other settings and conditions can be configured in their respective tabs as required, but the default settings within here will work

    Note: if you find that your scheduled task won't run, check out My Post on Powershell Execution Policies which may be preventing the scheduled task from running your powershell script

Friday, 28 March 2014

Check/Monitor Website URL with Powershell

Windows Powershell has a built in web request module that you can use to test/check the availability of website URL(s).

The full script to do this is below;

[string] $url = "http://www.google.com"
[net.httpWebRequest] $req = [net.webRequest]::create($url)
$req.method = "HEAD"
[net.httpWebResponse] $res = $req.getresponse()

The first line is self explanatory - simply put in the full URL for the website you wish to check. In this example we will be checking http://www.google.com

[string] $url = "http://www.google.com"

The second line is where we create the actual web request instance in Powershell - under the $req variable

[net.httpWebRequest] $req = [net.webRequest]::create($url)

On the third line, we change the request method to be "HEAD" which means that only header information for the requested URL is retrieved. This speeds up the web request process as it does not pull all the data from the web URL - only the header information. If you leave this line out, the default method is "GET".

$req.method = "HEAD"

On the last line we actually submit the web request, and store the response in the $res variable

[net.httpWebResponse] $res = $req.getresponse()

For a successful web request, the response should look something like this:

IsMutuallyAuthenticated : False
Cookies                 : {}
Headers                 : {Cache-Control, Content-Type, Date, Expires...}
ContentLength           : -1
ContentEncoding         : 
ContentType             : text/html; charset=ISO-8859-1
CharacterSet            : ISO-8859-1
Server                  : gws
LastModified            : 28/03/2014 10:24:19 AM
StatusCode              : OK
StatusDescription       : OK
ProtocolVersion         : 1.1
ResponseUri             : http://www.google.com.au/?gfe_rd=cr&ei=I7M0U5P_BenC8gfRvIHADg
Method                  : HEAD
IsFromCache             : False

Based on this, you could check the statuscode or statusdescription properties of $res to make sure the value matches "OK".

Alternatively, if you enter an invalid URL for a website that doesn't exist, you will get an error, and the value of $res will be $null. The error you get will be something along the lines of;

Exception calling "GetResponse" with "0" argument(s): "The remote server returned an error: (502) Bad Gateway."
At line:4 char:46
+ [net.httpWebResponse] $res = $req.getresponse <<<< ()
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException

Wednesday, 26 March 2014

Get Folder Size (including subfolders) with Powershell

The method for obtaining the size of all items within a folder using Windows Powershell is unfortunately not as straight forward as it probably should be. The method outlined below is very useful for querying multiple folders to determine the size of all items inside, including all subfolders and files.

$dir = "C:\temp"
$totaldirsize = (get-childitem $dir -recurse -force | measure-object -property length -sum)

The first variable sets the directory that we wish to query - in this example it is C:\temp.

Next, we use a get-childitem to query the directory. The -recurse and -force switches mean that all sub directories and files are also included. We then pipe the results to measure-object and calculate the length of each child item (which is actually the size in bytes) and use the -sum switch to add them all up.

If you run the above command, and then look at the $totaldirsize variable, you will see something like below;

PS C:\> $totaldirsize

Count    : 38
Average  : 
Sum      : 51652089
Maximum  : 
Minimum  : 

Property : length

So from this we can see there are 38 items in total in the C:\temp directory, and the sum of all files is 51652089 bytes. To make this more useful, we can easily convert this value to KB, MB or GB

$mbsize = $totaldirsize.sum / 1MB

If we now look at $mbsize, we'll usually have an integer with a large number of decimal places. In this example, the result I got was 49.2592706680298. So as a final step, I'd like to round this number to 2 decimal places

$mbsize2 = "{0:N2}" -f $mbsize

Now if we look at the value of $mbsize2, the result is 49.26.

You can change the 2 in {0:N2} to any other digit to round the number to that many places after the decimal point.

Working with Windows Services in Powershell

We can use the get-wmiobject method in order to retrieve properties of windows services on local or remote servers.

The query below is an example on how to get the properties of the Print Spooler service on a local machine (ie. the same machine that the script is being executed on)

To get the "Service Name" of a windows service, go to services.msc, select a service, open it's properties (right click > Properties) and look at the "Service Name" at the top. 

$serviceprops = get-wmiobject -class win32_service -filter "name='Spooler'"

If we then run $serviceprops we get the following information;

ExitCode  : 0
Name      : Spooler
ProcessId : 1628
StartMode : Auto
State     : Running

Status    : OK

This information could therefore be used to check the respective windows process ID, start mode, state and status of a service

The query below is an example on how to get the properties of the Print Spooler service on a remote machine (server1)

$serviceprops = get-wmiobject -computername "server1" -class win32_service -filter "name='Spooler'"

As you can see, the command is mostly the same, we have simply added the argument "-computername "server1"" to make the get-wmiobject query run on the remote server.

We can then take this one step further and perform an action (aka method) on a service, such as starting or stopping it. To view the available methods for the service, run a get-member on the $serviceprops variable;

$serviceprops | get-member

You will then see a list of available methods (and properties) for the variable.

Two of the most common methods for a service would be to start or stop the service. We simply append the respective methods onto the end of the $serviceprops variable (after it has run the get-wmiobject query above to get the service properties).

To start a service;
$serviceprops.startservice()

To stop a service;
$serviceprops.stopservice()

Friday, 8 February 2013

Querying Exchange Mailboxes with Powershell Pt 2

In my previous blog post, I outlined some methods to query Exchange mailboxes using the get-mailbox cmdlet. Whilst the information obtained using this cmdlet is very useful, it does not contain all the information that is available for an Exchange mailbox.

There is another cmdlet available called Get-MailboxStatistics. It is used/applied in the same way as the Get-Mailbox cmdlet, but contains different fields or properties, and therefore, different information relating to the mailbox.

If you run the command Get-MailboxStatistics -identity "MailboxName" | fl it will display the mailbox statistics for that particular mailbox.

This information includes;

  • Associated item count (ie. number of items within the mailbox)
  • Deleted item count
  • Last logon/logoff time
  • Total size of items
  • Total size of deleted items
So, for a practical example that you could potentially use to query this information. Let's say you want to query all your mailboxes to identify those that have more than 20,000 items within them. You could run the following command;

Get-Mailboxstatistics | Where {$_.ItemCount -gt "20000"} | Select DisplayName

You could replace $_.ItemCount with any of the other properties that are contained within the Get-MailboxStatistics cmdlet.

If you want the query to return more than just the DisplayName field for objects that match your query, you can add them after DisplayName and separate them with a comma (,), or if you want to return all properties then remove | Select DisplayName altogether.

You can also add | Export-CSV-path "C:\Export.csv" -notype to the end to export your query results to a .csv file.

Tuesday, 5 February 2013

Querying Exchange Mailboxes with Powershell

Exchange and Powershell go hand in hand - as it is essentially what Exchange is built on top of, and is what the Exchange Management Console (EMC) uses to execute commands you choose through the GUI. (Ever noticed you get the Powershell command line equivalent for commands you execute in EMC?)

One of the most common things an administrator will need to do is query all the mailboxes within the system for certain criteria, which I will explain and breakdown in this blog entry.

The command Get-Mailbox is one of the most useful and powerful commands you can use (for information gethering). It can be used to retrieve all the properties for all the mailboxes in your system, or can be tweaked to only return some of the properties for some of the mailboxes within your system.

If you execute the command Get-Mailbox on its own, you will probably be flooded with 1000 responses, followed by the following warning message;


WARNING: By default only the first 1000 items are returned. To change the number of items returned, specify the parameter "-ResultSize". To return all items specify "-ResultSize Unlimited" (Note: Returning all items may take a 
very long time and consume a large amount of memory depending on the actual number of items). It is not recommended to store the results in a variable; instead pipe the results to another task or script to perform batch changes

As per the message, the command on its own will only return the first 1000 results. You can change this by adding -ResultSize Unlimited to it, so it would look like this;

Get-Mailbox -ResultSize Unlimited

If you run this command it will run through and display the first few properties for every mailbox within your system. While this can be useful, the command can be further tweaked to run faster and provide more accurate/informative results.

To get all the properties for a single users mailbox, you can use the following command

Get-Mailbox -Identity "Morrissey, Peter"

You can replace what is between the " " with any identifier for the mailbox - such as Primary SMTP Address, Display Name or Alias.

When you run this command it will only display the first few properties as it is presenting the information in a table format. If you change the command to display in list format (by adding | fl), you get MUCH more information:

Get-Mailbox -Identity "Morrissey, Peter" | fl

Now you can view ALL the properties for an individual users mailbox. These properties apply to every mailbox within the system, so you can once again, modify the Get-Mailbox command to search on any of the properties you see in this list.

For example, if I wanted to query all the mailboxes within my system, and only return those whose 'Name' property contain the text "Peter", I would run the following command

Get-Mailbox -ResultSize Unlimited | Where {$_.Name -like "*Peter*"}

You still need to include -Resultsize Unlimited because although you aren't expecting more than 1000 results to be returned, you do want the Get-Mailbox command to query more than the first 1000 mailboxes, which it will not do if this omitted.

You can replace the $_.Name with any of the other properties for a mailbox - it just needs to have the $_. before it.

The -like operator can also be changed to use -eq (equal to) -ne (not equal to) -gt (greater than) -lt (less than) -le (less than or equal to) -ge (greater than or equal to)

Next you can fine tune the properties that you wish to display for each object whos name contains "Peter". Say you want to only display the DisplayName, Alias, Database and PrimarySMTPAddress properties for each of the results. You could use the following line

Get-Mailbox -ResultSize Unlimited | Where {$_.Name -like "*Peter*"} | Select DisplayName, Alias, Database, PrimarySMTPAddress

The benefit of doing this is that by retrieving fewer properties for each object, it speeds up the time it takes for the query to run. It also makes looking through the results much faster as you aren't reading over information you don't need. Once again, you can replace the properties I used with any of the properties available for each mailbox, as we discovered earlier.

Finally, you  can export the results of your query to a CSV file

Get-Mailbox -ResultSize Unlimited | Where {$_.Name -like "*Peter*"} | Select DisplayName, Alias, Database, PrimarySMTPAddress | Export-CSV -path "C:\Export.csv" -notype

Once you have the information in a CSV file you can attach it to an email and send it to yourself (or someone else) as per my previous blog post

Creating Custom Tables in Powershell

When working with scripts, it is often necessary to collect, store and present information in a table format. An example of this could be to export a number of properties from a users mailbox in Exchange into a table format.

For my example, I will run the following command to export all the properties of a mailbox (mine) to a variable called $UserList

$UserList = get-mailbox -ResultSize Unlimited | Where-Object {$_.DisplayName -eq "Morrissey, Peter"}

The next thing to do is to declare/create the table. It can be called anything you like, but I tend to use $report. The following line of code creates the table;

$report = @()

Next, you need to populate each row of your table with whatever fields you require. This would usually be done with a ForEach statement where you cycle through a list of objects, with numerous properties for each object.

In my example, the object list is stored in $UserList. I can run the command $UserList | fl to display the list of all the properties available for each object in my variable.

This ForEach command will cycle through each entry in $UserList (in my example it is only a single entry for my mailbox) and add whatever properties of that object I declare into the table (I have used DisplayName and Database) but you can use as many as you like - they just need to be separated by a semi colon (;)

ForEach ($User in $UserList)
             {
             $report += New-Object psobject -Property @{DisplayName=$($User.DisplayName);Database=$($User.Database)}
             }

This is a simple example and will create a table with two values for each row; DisplayName and Database.

Once you have populated all the rows you require in your table, you can then export it to a CSV file using the following command;

$report | select DisplayName, Database | Export-CSV -path "C:\ReportOutput.csv"

Once again, you will need to replace $report with whatever variable you used to declare your table. You also need to use the Select component to select all the table fields you wish to export into the CSV file - in my case it was the DisplayName and Database fields.

Finally, once the CSV file is exported, you can then attach it to an email and send it to yourself (or someone else) as outlined in my previous blog post.

Tuesday, 29 January 2013

File Locked After Sending SMTP Email with Powershell

In my previous blog post about sending emails with Powershell, I mentioned a problem where files that you attach to an email become locked until the instance of Powershell you are running has exited completely. So if you run a script through Powershell ISE that attaches a file to an email, that file will remain locked until you exit Powershell ISE.

If the file is locked by Powershell, you will get an error/warning message similar to the following if you try to modify it in any way;

The process cannot access the file 'c:\filename.txt' because it is being used by another process

By using the following command, you can ensure that Powershell 'disposes' of the email message once it has been sent and does not continue to 'lock' any files you attach and send via email;

$mailmessage.dispose()

Note: this is assuming that $MailMessage = New-Object system.net.mail.mailmessage 

Friday, 25 January 2013

Sending Email Messages with Powershell

A lot of the scripts I use generate reports or .csv files that I need to reference or send to other users who have requested them. One of the easiest ways to accomplish this is to configure your script to automatically email yourself (or anyone else) with the results and/or any files that were created in your script. This saves you time by not having to manually copy the files to another location or attach them to a new email message. You can simply receive the email message and forward it on, or include all recipients in the original email sent by the script.

The following lines of code can be used to create and send an email message with Windows Powershell:


$SmtpClient = new-object system.net.mail.smtpClient 
$MailMessage = New-Object system.net.mail.mailmessage 
$SmtpClient.Host = "your.smtp.server" 
$mailmessage.from = ("fromaddress@yourdomain.com") 

$mailmessage.Subject = “Subject”
$mailmessage.Body = "body text"
$mailmessage.IsBodyHtml = $true
$mailmessage.ReplyTo = "replytoaddress@yourdomain.com"
$mailmessage.Attachments.Add("C:\Path\yourfile.txt")
$smtpclient.Send($mailmessage)
$mailmessage.dispose()

Notes:

You will need to replace the values in some of the variables above to allow it to work in your environment (ie. SmtpClient.Host, FromAddress, Subject, Body text, Reply to address, attachment path).

You will also need to ensure that whatever machine you are running this script on is "authorised" to send with the SMTP server you have configured.

The final line of code $mailmessage.dispose() is very important when sending emails with attachments. If this line is not included, Powershell 'locks' the file(s) you attached after the message has been sent and will not 'release' them until the Powershell instance you are running is closed.

Thursday, 24 January 2013

Exporting Exchange Distribution Group Members with Powershell

Before making any major changes to distribution groups, I like to take a backup of the current membership lists prior to making any changes. Particularly in cases where membership lists are quite large, it's always better to be safe than sorry for when a user or distribution group owner wants to know who was a member prior to making the change.

Getting the list of members and exporting it to a simple .csv file can be achieved using the Powershell script below.

Note that you should change the first two (2) variables ($DLName and $ExportFilePath) accordingly.

#Script to get/export list of all distribution group members
#Date Created: 24th January, 2013
#Author: Peter Morrissey


#Enable Exchange cmdlets
add-pssnapin *exchange* -erroraction SilentlyContinue

$DLName = "DL_Example_List"
$ExportFilePath = "C:\Export\Export.csv"


$MemberList = get-distributiongroupmember -identity $DLName

ForEach ($Member in $MemberList){write-output "$($member.name)" | out-file -filepath "$exportfilepath" -append -noClobber}

Friday, 18 January 2013

Enabling Exchange Cmdlets in Powershell

In order to run Exchange Server commands within a Powershell script, the relevant cmdlets need to be 'called' within Powershell.

This is accomplished with the following line, which is found at the start of nearly all of my Powershell scripts.

#Enable Exchange cmdlets
add-pssnapin *exchange* -erroraction SilentlyContinue

If you attempt to run Exchange commands in Powershell (eg. Get-Mailbox) without loading the Exchange cmdlets first, you will get an error similar to the following;


The term 'get-mailbox' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:12
+ get-mailbox <<<< 
    + CategoryInfo          : ObjectNotFound: (get-mailbox:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException