tag:blogger.com,1999:blog-67792891651966469002024-03-20T21:55:04.278+11:00Peter Morrissey's Technology & Scripting BlogHints, tips, thoughts & findings on the world of Technology & ScriptingPeter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.comBlogger84125tag:blogger.com,1999:blog-6779289165196646900.post-55840866725175939082021-12-10T13:57:00.013+11:002022-01-04T11:55:30.552+11:00Microsoft Exchange Server - FIPS-FS Error 0x800706BE, 0x80010105<p>We started encountering this issue on our on-prem (Hybrid) exchange server all of a sudden which was preventing emails from being scanned by the anti malware module, and hence were being held in the submission queue and not being delivered.</p><p>You may see errors in the event log such as;</p><p><b>The FIP-FS Scan Process failed initialization. Error: 0x80010105. Error Details: The server threw an exception.</b></p><p><b>The FIP-FS Scan Process failed initialization. Error: 0x800706BE. Error Details: The remote procedure call failed.</b></p><p><b>Fault bucket , type 0<br /></b><b>Event Name: APPCRASH<br /></b><b>Response: Not available<br /></b><b>Cab Id: 0<br /></b><b>Problem signature:<br /></b><b>P1: scanningprocess.exe</b></p><p>The usual server and service restarts did not fix the problem.</p><p>The issue appears to be related to updates not being installed correctly for the exchange anti malware scanning module.</p><p>You can check/confirm this by running these commands from Powershell;</p><p><span style="color: #04ff00; font-family: courier;">Add-PsSnapin Microsoft.Forefront.Filtering.Management.Powershell</span></p><p><span style="color: #04ff00; font-family: courier;">Get-EngineUpdateInformation</span></p><p><span style="font-family: inherit;">We got a response like below - note the "UpdateAttemptFailed" status</span></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiznM_w9AVq5lveEM2EBXAHQNuEiUpu-06pp7gOUpTQNZZbr5IBczGZZHbre6vpulx9CIzjr2ZU6F887E0m0w4KqBSyrf4JE8wF4Mkcsdh6b5rn8RCWR3mxixmdwOawKxNYJYWb9cIsyYGo/s377/get-engineupdateinformation.png" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="178" data-original-width="377" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiznM_w9AVq5lveEM2EBXAHQNuEiUpu-06pp7gOUpTQNZZbr5IBczGZZHbre6vpulx9CIzjr2ZU6F887E0m0w4KqBSyrf4JE8wF4Mkcsdh6b5rn8RCWR3mxixmdwOawKxNYJYWb9cIsyYGo/s16000/get-engineupdateinformation.png" /></a></div><br /><span style="font-family: inherit;"><br /></span><p></p><p><span style="font-family: inherit;"><br /></span></p><p><span style="font-family: inherit;"><br /></span></p><p><span style="font-family: inherit;"><br /></span></p><p><span style="font-family: inherit;"><br /></span></p><p><span style="font-family: inherit;"><br /></span></p><p><span style="font-family: inherit;">You can also check the update engine settings by running the command</span></p><p><span style="color: #04ff00; font-family: courier;">get-engineupdatecommonsettings</span></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsT-2uwMK39s-piJtQTau9CjSZHVK7PUcNzSaqYZQOz1sctD8cF-qzJ9kmZJcLDIY7aucBh-ILH7lqXu4McNzsBvXucIhPWWN_OgZn3VCdBpD_tbN94lkwbiZI8wo8G2qDY5msAc8YRvS-/s599/get-engineupdatecommonsettings.png" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="195" data-original-width="599" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsT-2uwMK39s-piJtQTau9CjSZHVK7PUcNzSaqYZQOz1sctD8cF-qzJ9kmZJcLDIY7aucBh-ILH7lqXu4McNzsBvXucIhPWWN_OgZn3VCdBpD_tbN94lkwbiZI8wo8G2qDY5msAc8YRvS-/s16000/get-engineupdatecommonsettings.png" /></a></div><br /><p><br /></p><h3 style="text-align: left;"><br /></h3><h3 style="text-align: left;"><br /></h3><h3 style="text-align: left;"><br /></h3><h3 style="text-align: left;"><br /></h3><div>Note the PrimaryUpdatePath - when we attempted to access this URL we were getting a 404 error - this is potentially what the problem is?<br /><br /></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitfja2HsvUYr06JDZQyfcXvoU1577eF6w37472AhCQcrBBDDyb7H9uo2_qKEaRLSbiPJHMiRA2HQLP_8i8PF4y0j4XdhYcO87c4Pngp0obLZXGsyu6hQuNTzJjt6rM2Um_G_glkK8eHBQd/s1239/2021-12-10+13_57_12-The+resource+cannot+be+found..png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="197" data-original-width="1239" height="102" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitfja2HsvUYr06JDZQyfcXvoU1577eF6w37472AhCQcrBBDDyb7H9uo2_qKEaRLSbiPJHMiRA2HQLP_8i8PF4y0j4XdhYcO87c4Pngp0obLZXGsyu6hQuNTzJjt6rM2Um_G_glkK8eHBQd/w640-h102/2021-12-10+13_57_12-The+resource+cannot+be+found..png" width="640" /></a></div><br /><div><br /></div><h3 style="text-align: left;">Update 4/1/22</h3><div>I found the following article from the Exchange Team Blog that references this issue and provides a script to fix it</div><div><br /></div><div><a href="https://techcommunity.microsoft.com/t5/exchange-team-blog/email-stuck-in-exchange-on-premises-transport-queues/ba-p/3049447/page/2">https://techcommunity.microsoft.com/t5/exchange-team-blog/email-stuck-in-exchange-on-premises-transport-queues/ba-p/3049447/page/2</a></div><div><br /></div><div>Before implementing the provided fix, I can ran the default script to Enable-AntimalwareScanning (located in C:\Program Files\Microsoft\Exchange Server\V15\Scripts) - mail delivery operated for a brief period of time before mails began queuing again.</div><div><br /></div><div>I ran the script provided in the blogpost, and even after a full server restart, the problem still persists so I have disabled the antimalware scanning again</div><div><br /></div><h3 style="text-align: left;">Fix/Solution/Workaround</h3><div>Disable the anti-malware scanning option. This is easily done by running the included powershell script in your Exchange installation directory</div><div><br /></div><div>C:\Program Files\Microsoft\Exchange Server\V15\Scripts\Disable-AntimalwareScanning.ps1</div><div><br /></div><div>Run the script then restart the Microsoft Exchange Transport Service</div><div><br /></div><div>Once we did this the submission queue immediately started clearing.</div><div><br /></div><div>You can reverse the change easily by running the Enable-AntimalwareScanning.ps1 script in the same folder later on.</div><div><br /></div><div><br /></div><div>We've also found this recent MS article outlining steps to manually update the scan engines used by Exchange Server, but we found it still failed to update the signatures after running it</div><div><br /></div><div>https://docs.microsoft.com/en-us/exchange/troubleshoot/setup/manually-update-scan-engines</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com10tag:blogger.com,1999:blog-6779289165196646900.post-31714978764655013072021-11-16T15:23:00.002+11:002021-11-16T15:23:54.493+11:00How to reclaim space from deleted files on windows server deduplication volume<p>Windows server has great deduplication functionality allowing for increased usable capacity on non-operating system volumes, by performing block level deduplication to increase storage efficiency. This is particularly useful for storing data such as backups, which often involves minimal changing data, so a prime candidate for effective deduplication.</p><p>The problem often encountered with deduplication volumes is that when files are deleted, the space is not immediately reclaimed by the operating system. A "garbage collection" process needs to be run in order for the deduplication engine to reclaim this space.</p><p>Thankfully, this is incredibly easy to do and can be done from a powershell command prompt - here's the command you need to run;</p><p><span style="color: #04ff00; font-family: courier;">start-dedupjob -type GarbageCollection -full -path e: -FastStart $true</span></p><div>Be sure to change the -path variable from E: to whatever drive you wish to run the garbage collection on. Also note that depending on the size of your volume, the garbage collection can take some time to initiate and complete, even with using the -FastStart switch.</div><div><br /></div><div>You can check the progress of your deduplication jobs by running the command</div><div><br /></div><div><span style="color: #04ff00; font-family: courier;">get-dedupjob</span></div>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-65725326280198604812021-11-02T08:06:00.001+11:002021-11-02T08:06:11.600+11:00Microsoft Exchange - 451 4.7.0 Temporary server error. Please try again later. PRX5 - FIX<p>I encountered this error on my on-premise/Hybrid exchange server today after noticing that internal mail relay was not working. When attempting to send a test email via telnet, I got the error <span style="color: red;">451 4.7.0 Temporary server error. Please try again later. PRX5</span></p><p>After rebooting the server, the error still persisted - so a quick bit of searching quickly lead to me adjusting the Exchange servers DNS settings.</p><p>From within Exchange Control Panel (ECP) - go to Servers (left hand menu) and double click your Exchange server to open it's properties</p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAohbil6XX6Ey4GapKqWWSgFCeNADP3oTVG7UmXLtVM-USvNsvECndrV-L9o0i91CqctUtSjjw85no4YEXq8FQOor-uYJgDV4ftlMcmhZPIt9uvPuyJCBBsGMdgrajLHHtZNL16SS0Ylh8/s884/2021-11-02+08_02_08-servers+-+Microsoft+Exchange+%25E2%2580%2594+Mozilla+Firefox.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="510" data-original-width="884" height="370" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAohbil6XX6Ey4GapKqWWSgFCeNADP3oTVG7UmXLtVM-USvNsvECndrV-L9o0i91CqctUtSjjw85no4YEXq8FQOor-uYJgDV4ftlMcmhZPIt9uvPuyJCBBsGMdgrajLHHtZNL16SS0Ylh8/w640-h370/2021-11-02+08_02_08-servers+-+Microsoft+Exchange+%25E2%2580%2594+Mozilla+Firefox.png" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;"></td></tr></tbody></table><br /><br /><p>Select <b>DNS Lookups</b> from the left menu.</p><p>By default, the option for External and Internal DNS lookups is usually <b>All network adapters (All available IPv4)</b>. I changed this to <b>Microsoft Hyper-V Network Adapter</b> and the problem was immediately resolved.</p>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com1tag:blogger.com,1999:blog-6779289165196646900.post-88670767712555682382021-08-20T08:00:00.003+10:002021-08-20T08:00:00.193+10:00Google Classroom - How to get a list of all classes/classrooms<p>You can use the GAM utility to easily export the details of all Google Classrooms in your domain by following the commands below. In this example we'll be exporting the details to a CSV file</p><p>(Be sure to update the path to gam.exe and the output CSV file location)</p><p><span style="color: #04ff00; font-family: courier;">$allclasses = C:\admin\gam\gam.exe print courses > "C:\Admin\Google Classroom\allclasses.csv"</span></p><div>This works well, but if you look at your CSV file you'll notice that firstly, it isn't formatted into columns properly, and secondly, there are a heap of fields called "coursematerial" that aren't required. We can filter out those fields by using the command below</div><div><br /></div><div><div><span style="color: #04ff00; font-family: courier;">$allclassesfiltered = import-csv "C:\Admin\GoogleClassroom\allclasses.csv" | select-object * -ExcludeProperty "courseMaterial*"</span></div></div><div><br /></div><div>We can then re-export the CSV file with the command below which will actually export it correctly as a CSV file with data separated into columns correctly</div><div><br /></div><div><div><span style="color: #04ff00; font-family: courier;">$allclassesfiltered | export-csv "C:\Admin\Google Classroom\allclasses-filtered.csv" -NoTypeInformation</span></div></div><div><br /></div><div><br /></div>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-53005389575895518252021-08-19T08:00:00.004+10:002021-08-26T13:29:22.980+10:00GAM - How To Sync Google Classroom Students from CSV File<p>GAM is great command line utility that fills a huge void in the management of Google Classroom - since Google haven't bothered after all these years to create any kind of centralised management interface for the product despite a huge uptake in use from COVID-19 and remote learning.</p><p>One of the downfalls of GAM is it can be a little slow - if you wanted to add students individually to a class, the command to add each student takes several seconds to execute - which doesn't sound like much, but in a large school with lots of students and lots of classes - it could take hours to complete.</p><p>Thankfully, they have included the ability to synchronise members (students) of a class from a CSV file. What they don't mention in their documentation though is how the CSV file should be formatted - kind of important, right?</p><p>The CSV file should be formatted as a basic list - with a single column and no column headings - containing the email addresses of the students you wish to add. If you opened it in Notepad - it would look like the screenshot below</p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigxd5TRmvbnpVZzB_BvWviLvnAnNXuWqNsg-EKGu4e5Yauem5dLyDnpjaIM_P1-XEOyTV5pK5Lr_DQRyPw-Qzu0I0zUj3FwFdtTZQcx1FkY1E7-2drVvyg9eUPn4AmDjkSNie47ifs9rMI/s492/2021-08-10+08_40_38-Window.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="269" data-original-width="492" height="175" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigxd5TRmvbnpVZzB_BvWviLvnAnNXuWqNsg-EKGu4e5Yauem5dLyDnpjaIM_P1-XEOyTV5pK5Lr_DQRyPw-Qzu0I0zUj3FwFdtTZQcx1FkY1E7-2drVvyg9eUPn4AmDjkSNie47ifs9rMI/s320/2021-08-10+08_40_38-Window.png" width="320" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Example CSV file format to sync students to Google Classroom with GAM</td></tr></tbody></table><br /><p>One thing to note - if you are going to sync from a CSV file, it will add all the students in the CSV file to the classroom, but it will also remove any students already in the classroom who aren't listed in the CSV file, so it's important your CSV file contains <b>all</b> the students in the class.</p><p>The command to run the sync is below;</p><p><span style="color: #04ff00; font-family: courier;">C:\admin\gam\gam.exe course "googlecourseid" sync students file "C:\admin\GoogleClassroom\class1.csv"</span></p><p>(Be sure to update the path to your gam.exe file, the google course ID and the CSV file location)</p>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-20827664177584044122021-08-18T08:00:00.001+10:002021-08-18T08:00:00.183+10:00Microsoft Teams - Add Members from Active Directory Group to Team<p>Teams may often need to be populated with the members of an existing Active Directory security or distribution group. You can use the powershell commands below to easily do this.</p><p>Firstly, we need to get the members of our Active Directory group - and pipe the result to the get-aduser cmdlet so we can include the mail property as this is the identifying field we need to use to add users to Teams.</p><p><span style="color: #04ff00; font-family: courier;">$grpmembers = Get-ADGroupMember -identity | Get-ADUser -properties mail</span></p><p>We will then enable connect to Microsoft Teams to enable the cmdlets, cycle through the list of members and add them to the team. Make sure you update the $teamgrpid to be the unique group ID for the team you wish to add the users to.</p><p><span style="color: #04ff00; font-family: courier;">Connect-MicrosoftTeams</span></p><p><span style="color: #04ff00; font-family: courier;">$teamgrpid = "123456"</span></p><p><span style="color: #04ff00; font-family: courier;">foreach ($grpmember in $grpmembers)<br /> {<br /><span> Add-TeamUser -groupid $teamgrpid -user $($grpmember.mail) -role user<br /></span><span> }</span></span><br /></p><p>You can change the role variable from <b>user</b> to <b>member</b> if you wish to add the users as members instead.</p><p><span><br /></span></p>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-17822827151446631372021-08-17T08:00:00.007+10:002021-08-17T08:00:00.188+10:00Install Active Directory Powershell Module in Windows 10<p>For whatever reason, Microsoft has now changed the way the powershell module for Active Directory Domain Services and Lightweight Directory Services tools.</p><p>Most documentation suggests you install this from the "Turn Windows features on or off" menu located under Programs and Features within Control Panel - but in later Windows 10 releases, the feature is no longer available. It was previously called "Role Administration Tools".</p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoJll0w0WXu-LXXFLxXg0sTrTgshe0panOC-64Uq7VJ7v38SIseilShs4MRpklsWH-CPlZLvOhI221_2r4f_r1MyiB26glQTllhVmrYH_APagjN2XzaSEjQqnRQ5BH7g5DM5OHdK0QjmnN/s413/2021-08-09+13_08_11-Window.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="364" data-original-width="413" height="282" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoJll0w0WXu-LXXFLxXg0sTrTgshe0panOC-64Uq7VJ7v38SIseilShs4MRpklsWH-CPlZLvOhI221_2r4f_r1MyiB26glQTllhVmrYH_APagjN2XzaSEjQqnRQ5BH7g5DM5OHdK0QjmnN/s320/2021-08-09+13_08_11-Window.png" width="320" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Role Administration Tools is no longer visible under Turn Windows Features on or off</td></tr></tbody></table><br /><p>Likewise, the previously used powershell command is no longer recognised</p><p><span style="color: #04ff00; font-family: courier;">Enable-WindowsOptionalFeature -Online -FeatureName RSATClient-Roles-AD-Powershell</span></p><p>You now get an error when running this command</p><p><span style="color: red; font-family: courier;">Enable-WindowsOptionalFeature : Feature name RSATClient-Roles-AD-Powershell is unknown.</span></p><div>So, how do you do it? Follow the steps below</div><div><ol style="text-align: left;"><li>Click the Start button then select <b>Settings</b></li><li>Select <b>Apps</b></li><li>Select <b>Optional Features</b></li><li>Click <b>Add a Feature</b></li><li>Search for "RSAT" and select the option for <b>RSAT: Active Directory Domain Services and Lightweight Directory Services Tools</b>. <br /><br /><br /><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_cKO9KvXyvHd-Nc8mb3jQfMeJApIfjHAjkoAoMGi2U3DV7PForLPNlJfazrgMdskXYPGbufhQAq2Ml3gddtZTDvsOiYrQehrv4e-Fzj79mbLz3HQWmQHJ2CK8VsVckpRHHx2RPEOVK9eZ/s756/2021-08-09+13_12_01-Window.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="756" data-original-width="679" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_cKO9KvXyvHd-Nc8mb3jQfMeJApIfjHAjkoAoMGi2U3DV7PForLPNlJfazrgMdskXYPGbufhQAq2Ml3gddtZTDvsOiYrQehrv4e-Fzj79mbLz3HQWmQHJ2CK8VsVckpRHHx2RPEOVK9eZ/w359-h400/2021-08-09+13_12_01-Window.png" width="359" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Select the option for RSAT: Active Directory Domain Services and Lightweight Directory Services</td></tr></tbody></table><br /></li><li>Click <b>Install</b></li></ol><div>If you get an error saying the install failed, make sure you aren't using a WSUS server as windows will try and install it from there by default if you are.</div></div><div><br /></div><div>To check, open regedit and go to <b>HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU</b> and set the <b>UseWUServer</b> key to <b>0</b></div><p><br /></p>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-61694288381919826162021-08-16T08:00:00.001+10:002021-08-16T08:00:00.197+10:00Microsoft Teams - Assign Teams Policies to Users with Powershell<p>Policies can be created within Microsoft Teams to manage permissions and restrictions on sets of users. In a school environment for example, you may wish to prevent the ability of students to call other users directly, but allow it for staff.</p><p>There are four (4) different types of policies that can be configured/applied to users in Teams;</p><p></p><ol style="text-align: left;"><li>Calling Policies</li><li>Meeting Policies</li><li>Channels Policies</li><li>Messaging Policies</li></ol><div>The policies can be created/configured within the Teams web admin interface.</div><div><br /></div><div>Calling Policies are configured under <b>Voice > Calling Policies</b></div><div>Meeting Policies are configured under <b>Meetings > Meeting Policies</b></div><div>Channels Policies are configured under <b>Teams > Teams Policies</b></div><div>Messaging Policies are configured under <b>Messaging Policies</b></div><div><br /></div><div>Once they're created and configured, they can be applied to users via powershell which can be quicker than doing it manually via the web admin interface - especially if you need to apply it to a large batch of users.</div><div><br /></div><div>The cmdlet we'll be using for this is <b>New-CsBatchPolicyAssignmentOperation</b></div><div><b><br /></b></div><div>Be sure to update the PolicyName and Identity arguments to match your policy and user</div><div><br /></div><h3 style="text-align: left;">Apply Teams Calling Policy to Users</h3><div><span style="color: #04ff00; font-family: courier;">New-CSBatchPolicyAssignmentOperation -PolicyType TeamsCallingPolicy -PolicyName "Name" -identity "user@domain.com"</span></div><div><br /></div><h3 style="text-align: left;">Apply Teams Meeting Policy to Users</h3><div><span style="color: #04ff00; font-family: courier;">New-CSBatchPolicyAssignmentOperation -PolicyType TeamsMeetingPolicy -PolicyName "Name" -identity "user@domain.com"</span></div><div><br /></div><h3 style="text-align: left;">Apply Teams Channel Policy to Users</h3><div><span style="color: #04ff00; font-family: courier;">New-CSBatchPolicyAssignmentOperation -PolicyType TeamsChannelsPolicy -PolicyName "Name" -identity "user@domain.com"</span></div><div><span style="color: #04ff00; font-family: courier;"><br /></span></div><h3 style="text-align: left;">Apply teams Messaging Policy to Users</h3><div><span style="color: #04ff00; font-family: courier;">New-CSBatchPolicyAssignmentOperation -PolicyType TeamsMessagingPolicy -PolicyName "Name" -identity "user@domain.com"</span></div><div><br /></div><h3 style="text-align: left;">Checking</h3><div>You can then check what policies are applied to a user by using the cmdlet below</div><div><br /></div><div><span style="color: #04ff00; font-family: courier;">Get-CsUserPolicyAssignment -identity "user@domain.com" </span></div><div><br /></div><div>This command will return a table displaying all the different policies that are applied to the user specified</div><p></p>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-90463676337340276112021-08-13T08:00:00.008+10:002021-08-13T08:00:00.193+10:00Microsoft Teams - Add Additional Team Owner using Powershell<p>When a user is added to a Microsoft Team, they are added as either a "member" or an "owner". Adding them as an "owner" gives them additional privileges/rights within the team - such as being able to add other members, delete chat messages etc.</p><p>If you add a user to a team using the "add-teamuser" powershell cmdlet, it will by default add them as a member. You can use a switch within the command to add them as an owner instead.</p><p>Here's an example;</p><p><span style="color: #04ff00; font-family: courier;">add-teamuser -groupid "abc123" -user "user@domain.com" -role owner</span></p><p>As you can see, the "-role owner" switch is what adds them as an owner to the team, instead of adding them as a member.</p><p>If the "-role" switch isn't specified, the user will be added as a member by default. You can alternatively include the "-role" switch and specify "member" as well - like the example below</p><p><span style="color: #04ff00; font-family: courier;">add-teamuser -groupid "abc123" -user "user@domain.com" -role member</span></p><p>This could be useful if you're using some kind of loop and want to add some users as members and some as owners and have the owner/member specified in a variable.</p>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-49300157911114394762021-08-12T08:00:00.020+10:002021-08-12T08:00:00.192+10:00Microsoft Teams - Copy Users Team Memberships to Another User<p>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.</p><p>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.</p><p>First, we need to connect to our O365 environment then import the session so we can access the cmdlets</p><p><span style="color: #04ff00; font-family: courier;">$ex = New-PSSession -ConfigurationName Microsoft.Exchange -Credential user@domain.com -ConnectionUri https://outlook.office365.com/powershell -Authentication basic -AllowRedirection</span></p><p><span style="color: #04ff00; font-family: courier;">import-pssession $ex</span></p><p>Next, let's get a list of all groups of the user we want to copy <u>from</u> - ie. the source user (be sure to adjust the source user variable to use your own target users email address)</p><p><span style="color: #04ff00; font-family: courier;">$sourceuser = "user1@domain.com"</span></p><p><span style="color: #04ff00; font-family: courier;">$Office365GroupsMember = Get-UnifiedGroup | where { (Get-UnifiedGroupLinks $_.Alias -LinkType Members | foreach {$_.primarysmtpaddress}) -contains $sourceuser}</span></p><div><br /></div><div>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</div><div><br /></div><div>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</div><div><br /></div><div><span style="color: #04ff00; font-family: courier;">$o365teamsgroups = $office365GroupsMember | where {$_.serviceendpointuris -like "*MicrosoftTeams*"}</span></div><div><br /></div><div>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.</div><div><div><br /></div><div><span style="color: #04ff00; font-family: courier;">Connect-MicrosoftTeams</span></div></div><div><span style="color: #04ff00; font-family: courier;">$targetuser = "user2@domain.com"</span></div><div><span style="color: #04ff00; font-family: courier;"><br /></span></div><div><span style="color: #04ff00; font-family: courier;">foreach ($o365team in $o365teamgroups)</span></div><div><span style="color: #04ff00; font-family: courier;"><span> {</span><br /></span></div><div><span><div><span style="color: #04ff00; font-family: courier;"> $teamprops = get-team -displayname $($o365team.displayname)</span></div><div><span style="color: #04ff00; font-family: courier;"> write-host "Adding $targetuser to $($teamprops.displayname) team"</span></div><div><span style="color: #04ff00; font-family: courier;"> add-teamuser -groupid $($teamprops) -user $targetuser</span></div><div><span><span style="color: #04ff00; font-family: courier;"> }</span></span><br /></div></span></div>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-65839074135224941572021-08-11T08:00:00.000+10:002021-08-11T08:00:00.214+10:00Microsoft Teams - How to Archive Teams using Powershell<p>Microsoft Teams has a great feature that allows users (and administrators) to archive teams instead of deleting them when they are no longer required. This can be convenient as archived teams are no longer visible to users, but can easily be re-activated (or unarchived) if they, or their contents are required in the future - which isn't possible if you delete a team.</p><p>To archive a team, use the command below</p><p><span style="color: #04ff00; font-family: courier;">Connect-MicrosoftTeams</span></p><p><span style="color: #04ff00; font-family: courier;">Set-TeamArchivedState -groupid "teamgroupidnumber123" -Archived:$true</span></p><p>Be sure to update the groupid with the unique groupid for the team you wish to archive. If you don't know what the groupid is, run the cmdlet below</p><p><span style="color: #04ff00;">Get-Team -displayname "Team Name"</span></p><p>The GroupID will be displayed in the matching results</p><p>After the team is archived you can still view it through the Teams Admin Console</p>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-87476249457163758402021-08-10T08:00:00.001+10:002021-08-10T08:00:00.171+10:00Microsoft Teams - Get Team ID/Group ID using Powershell<p>Many Microsoft Teams commands for powershell require you to specify the group id for a team so the script knows which one of your teams you wish to work on. The Group ID/Team ID is a unique string of letters and numbers which is why it's commonly used to ensure only one team would ever be targeted by a command.</p><p>Use the command(s) below to get the group id for one, or all of your teams.</p><p><span style="color: #04ff00; font-family: courier;">Connect-MicrosoftTeams</span></p><p><span style="color: #04ff00; font-family: courier;">get-team -displayname "Test Class"</span></p><p>You should get a response with the GroupID shown like in the example below;</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhF48rs2AuQhmw2oLMZ4hO_ajroiXnImrBhVVZBjUxwV8LNZcLX0TtWnweJaHVBcSmGcYouvB7P0NVmpOoIuSqFyzRn6UNgXnQabelDkOvjQqmo6Xy-vQFv3YD2aYuPkvDGhv96gbye7yUB/s843/2021-08-04+13_32_27-oakvs-scripts+-+Remote+Desktop+Connection.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="76" data-original-width="843" height="58" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhF48rs2AuQhmw2oLMZ4hO_ajroiXnImrBhVVZBjUxwV8LNZcLX0TtWnweJaHVBcSmGcYouvB7P0NVmpOoIuSqFyzRn6UNgXnQabelDkOvjQqmo6Xy-vQFv3YD2aYuPkvDGhv96gbye7yUB/w640-h58/2021-08-04+13_32_27-oakvs-scripts+-+Remote+Desktop+Connection.png" width="640" /></a></div><br /><p>Simple, right? What if I wanted to get the properties for all of my teams? Try the command below</p><p><span style="color: #04ff00; font-family: courier;">get-team</span></p><p>You can even store the groupid in a variable so it's easier to reference in subsequent commands. In this example, we'll store the properties in the $teamprops variable;</p><p><span style="color: #04ff00; font-family: courier;">$teamprops = get-team -displayname "Test Class"</span></p><p>You can then use the $teamprops.groupid property in another command - for example;</p><p><span style="color: #04ff00; font-family: courier;">add-teamuser -groupid $teamprops.groupid -user "user@domain.com"</span></p>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-38253587890932129552021-08-09T08:00:00.001+10:002021-08-09T08:00:00.173+10:00Microsoft Teams - Add Members to Private Channel using Powershell<p>If you have a large number of members you need to add to a private channel in teams, you may wish to use powershell to do so as it can be much faster than manually adding users via the Teams app, or web management interface.</p><p>Note that you will need to install the preview/prerelease version of the Microsoft Teams powershell module to get access to the cmdlet that we're using here (refer to my separate blog post on how to do this)</p><p><span style="color: #04ff00; font-family: courier;">Add-TeamChannelUser -GroupID "abc123456789" -Displayname "privatechannelname" -user "user@domain.com"</span></p><p>Be sure to update the Group ID with the unique Group ID of your target team - and the displayname should match the displayname of the private channel you wish to add the user to.</p><p>Note that adding users via this cmdlet can cause them to not show in the actual team for some time (in my experience 15-30 mins) so you may need to allow some time before checking/confirming they appear in the channel user list</p>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-1866503240750294082021-08-08T08:00:00.001+10:002021-08-08T08:00:00.221+10:00Microsoft Teams - Install Preview/Prerelease Powershell Module<p>Microsoft Teams has a preview/prerelease version of their powershell module available which has some commands that aren't contained in the general/public release.</p><p>If you wish to install the prerelease module, you can use the command below to do so</p><p><span style="color: #04ff00; font-family: courier;">Install-Module -Name MicrosoftTeams -AllowPrerelease -force</span></p><p>You may need to restart your powershell session and import the module again to get the required cmdlets.</p><p>If you get an error saying the 'allowprerelease' argument isn't recognized, then run the below commands ot update the PackageManagement and PowershellGet modules</p><p><span style="color: #04ff00; font-family: courier;">Install-Module -Name PackageManagement -Repository PSGallery -Force<br />Install-Module -Name PowerShellGet -Repository PSGallery -Force<br /></span></p><p>Restart your powershell session then run the install-module command again and it should complete successfully.</p>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-7946466174299217652021-08-07T08:00:00.006+10:002021-08-07T08:00:00.197+10:00Powershell - a parameter cannot be found that matches parameter name 'AllowPrerelease'<p>Sometimes when installing powershell modules, you may wish to utilise pre-release or preview versions of packages. Depending on what version of powershell you have installed, you may get an error message like the one below when attempting to install preview/prerelease packages;</p><p><span style="color: #04ff00; font-family: courier;">Install-Module : A parameter cannot be found that matches parameter name 'AllowPrerelease'.</span></p><p><span style="color: #04ff00; font-family: courier;">At line:1 char:37</span></p><p>To fix the problem you need to force an update of a couple of Powershell modules - namely PowershellGet and PackageManagement. </p><p>Run the commands below from an elevated powershell command prompt;</p><p><span style="color: #04ff00; font-family: courier;">Install-Module -Name PowerShellGet -Repository PSGallery -Force<br />Install-Module -Name PackageManagement -Repository PSGallery -Force</span></p><div>Exit/close your Powershell window/session then re-open and run the install-module command again - it should now complete successfully</div>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-40305713897582963412021-08-06T08:00:00.006+10:002021-08-06T13:13:55.010+10:00Microsoft Teams - teamchanneluser cmdlet is not recognized<p>Microsoft Teams features some commands (cmdlets) that are not readily available in the public/general release version of the Microsoft Teams powershell module. Some of these commands are;</p><p>get-teamchanneluser</p><p>add-teamchanneluser</p><p>If you attempt to run these commands, you'll get an error message like the one below;</p><p><span style="color: red; font-family: courier;">get-teamchanneluser : The term 'get-teamchanneluser' 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.</span></p><p><span style="color: red; font-family: courier;">At line:1 char:1</span></p><p>To fix the problem you will need to install the preview release of the Microsoft Teams powershell module. You can check the release version numbers from the website below</p><p>https://www.powershellgallery.com/packages/MicrosoftTeams/2.0.0</p><p>To install the preview release version, make note of the version number then run the command below</p><p><span style="color: #04ff00; font-family: courier;">Install-Module -Name MicrosoftTeams -AllowPrerelease</span></p><p>To fix the problem you need to force an update of a couple of Powershell modules - namely PowershellGet and PackageManagement. </p><p><span style="color: #04ff00; font-family: courier;">Install-Module : A parameter cannot be found that matches parameter name 'AllowPrerelease'.</span></p><p><span style="color: #04ff00; font-family: courier;">At line:1 char:37</span></p><p>Run the commands below from an elevanted powershell command prompt;</p><p><span style="color: #04ff00; font-family: courier;">Install-Module -Name PowerShellGet -Repository PSGallery -Force<br />Install-Module -Name PackageManagement -Repository PSGallery -Force</span></p><p><span style="color: #04ff00; font-family: courier;"></span></p><div>Exit/close your Powershell window/session then re-open and run the install-module command again - it should now complete successfully</div><p>You can confirm what version is installed by running the commands</p><p><span style="color: #04ff00; font-family: courier;">import-module "MicrosoftTeams"</span></p><p><span style="color: #04ff00; font-family: courier;">get-module</span></p><p>Look for the version number for the "MicrosoftTeams" module</p>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-21284647285850673572021-08-05T08:00:00.002+10:002021-08-06T13:13:38.295+10:00Microsoft Teams - The term is not recognized as the name of a cmdlet..<p>If you get an error message like the one below when attempting to issue commands like "connect-microsoftteams" - it's because you haven't got the MS Teams cmdlets installed</p><p><span style="color: red; font-family: courier;">Connect-MicrosoftTeams : The term 'Connect-MicrosoftTeams' 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.</span></p><p><span style="color: red; font-family: courier;">At line:1 char:1</span></p><p>To install the Microsoft Teams Powershell cmdlets, enter the command below. Make sure you run this from an elevated/administrator powershell prompt</p><p><span style="background-color: white; color: #04ff00; font-family: courier;">install-module -name MicrosoftTeams -force</span></p><p>If you don't run it as administrator, you'll get a message like this;</p><p><span style="color: red;">install-module : Administrator rights are required to install modules in 'C:\Program Files\WindowsPowerShell\Modules'. Log on to the computer with an account that has Administrator rights, and then try again, or install </span></p><p><span style="color: red;">'C:\Users\username\Documents\WindowsPowerShell\Modules' by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator).</span></p><p>The installation may take a minute or two to complete, you can confirm the module has been installed by running the following command</p><p><span style="color: #04ff00;">import-module "MicrosofTeams"</span></p><p>Then run the command</p><p><span style="color: #04ff00;">get-module</span></p><p>A list of available modules will be displayed which should include MicrosoftTeams like the screenshot below</p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOSKr-7gCy6a5Vi37Lsp0Yt1EIb8BXy6c5RkTpx6wv8PfcXQJnOSYr6QaJu-CFiynxvEd6RnrA3tQH1pRVxhFn9br1ZqO57R_e_Zdwwl7oiKvFz4odfytOGk0nGJ_LzekCiJH1N6rshkLn/s1076/2021-08-04+12_18_55-Administrator_+Windows+PowerShell+ISE.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="156" data-original-width="1076" height="93" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOSKr-7gCy6a5Vi37Lsp0Yt1EIb8BXy6c5RkTpx6wv8PfcXQJnOSYr6QaJu-CFiynxvEd6RnrA3tQH1pRVxhFn9br1ZqO57R_e_Zdwwl7oiKvFz4odfytOGk0nGJ_LzekCiJH1N6rshkLn/w640-h93/2021-08-04+12_18_55-Administrator_+Windows+PowerShell+ISE.png" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">get-module command output showing MicrosoftTeams module available</td></tr></tbody></table><br /><p><br /></p>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-64792906690707004652021-08-04T10:59:00.006+10:002021-08-04T11:15:30.618+10:00Microsoft Teams - Powershell Administration Commands<p> Microsoft Teams has a powershell module available that allows you to perform just about all Teams management tasks using a script. This is very convenient for bulk operations, like creating lots of teams and keeping team memberships up to date - especially since the Teams web admin interface can be quite slow and cumbersome.</p><p>Here's a quick guide on some of the most common commands and uses</p><h3 style="text-align: left;">Install Microsoft Teams Powershell Module</h3><div>Use the command below to install the Microsoft Teams powershell cmdlets</div><div><br /></div><div><span style="color: #04ff00; font-family: courier;">install-module -name MicrosoftTeams -force</span></div><h3 style="text-align: left;">Create a Team</h3><div>I like to create new teams by using a variable ($newteam) - this makes additional tasks easier since you can reference the unique group ID for the new team directly from the $newteam variable.</div><div><br /></div><div><span style="color: #04ff00; font-family: courier;">$newteam = new-team -displayname "Test Team" -mailnickname "Test-Team" -owner "user@domain.com"</span></div><div><br /></div><div>Update the properties for the display name, mailnickname (can't have spaces) and the team owner</div><h3 style="text-align: left;">Add a User/Member to a Team</h3><div>If you've created the team like I have above and used a variable like $newteam, you can then use the properties for this variable for other tasks like this</div><div><br /></div><div><span style="color: #04ff00; font-family: courier;">add-teamuser -groupid $newteam.groupid -user "user@domain.com"</span></div><h3 style="text-align: left;">Add an additional Owner to a Team</h3><div><span style="color: #04ff00; font-family: courier;">add-teamuser -groupid $newteam.groupid -user "user@domain.com" -role owner</span></div><div><br /></div><h3 style="text-align: left;">Remove User/Member from a Team</h3><div><span style="color: #04ff00; font-family: courier;">remove-teamuser -groupid $newteam.groupid -user "user@domain.com"</span></div><div><br /></div><div><br /></div>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-58606073697175532422021-05-27T08:40:00.002+10:002021-05-27T08:40:31.456+10:00Searching for String within Multiple Files using Powershell<p> I recently had a situation where I had to search for a particular string within a large number of files. More specifically, I was looking for what file contained a set of text used for a menu on a website - the problem is I didn't know which file contained the text, but I knew what I was looking for. The web server had a large number of xml, html and css files, so I quickly put together a powershell script to search these files quickly and was able to find what I was looking for within a matter of minutes! Certainly faster than manually opening files and performing a CTRL + F find within them.</p><p><span style="color: #04ff00;"><span style="font-family: courier;">$files = get-childitem -path "C:\inetpub\wwwroot\*.css" -recurse<br /></span><span style="font-family: courier;">foreach ($file in $files)<br /></span><span style="font-family: courier;"> {<br /></span><span style="font-family: courier;"> $filecontent = get-content $($file.fullname)<br /></span><span style="font-family: courier;"> $lookup = $filecontent | select-string "What I'm Looking For"<br /></span><span style="font-family: courier;"> if ($lookup)<br /></span><span style="font-family: courier;"> {write-host "$($file.fullname)"}<br /></span><span style="font-family: courier;"> }</span></span></p><p>The first line searches the folder C:\inetpub\wwwroot for any file ending in .css. The -recurse switch means it searches all subfolders within C:\inetpub\wwwroot as well. You can of course change the search directory and file type being searched for</p><p><span style="color: #04ff00; font-family: courier;">$files = get-childitem -path "C:\inetpub\wwwroot\*.css" -recurse</span></p><p>The next section is a ForEach loop that goes through every file that is found within $files (as outlined above). The content of the file is read into the $filecontent variable</p><p><span style="color: #04ff00; font-family: courier;">$filecontent = get-content $($file.fullname)</span></p><p>Next we perform a search of all the text within the $filecontent variable for a particular string of text. In this example the text we're looking for is "What I'm Looking For" - change this to whatever string of text you are trying to find tin these files. The result of the search is stored in the $lookup variable. If no match is found then $lookup will be null.</p><p><span style="color: #04ff00; font-family: courier;">$lookup = $filecontent | select-string "What I'm Looking For"</span></p><p>Finally, if a result is found and the $lookup variable is not null, the full name of the file is written to the screen</p><p><span style="color: #04ff00; font-family: courier;">if ($lookup)<br /></span><span style="color: #04ff00; font-family: courier;"> {write-host "$($file.fullname)"}</span></p><p>This little script saved me a heap of time - as built in windows search functions are pretty unreliable and don't search for content within files. Hope it is able to help you as well.</p>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-91328807615918855902021-05-11T08:00:00.004+10:002021-05-11T08:00:00.182+10:00Managing Google Classroom with GAM and Powershell<p>Part of my job requires me to actively manage our instance of Google Classroom - the adding/removal of teachers, students, changing of class owners are just a couple of the every day tasks that are required. Unfortunately, Google Classroom doesn't have an included management interface for doing this - but there is a great command line utility called "GAM" which I use to accomplish these tasks.</p><p>I looked at a few different management utilities to do this - and essentially left GAM until last as I was really after a reliable (and free) utility that had a graphical user interface (GUI) instead of command line, but unfortunately at the time I was looking there was nothing that met these requirements (and worked) for me.</p><p>GAM can be downloaded from <a href="https://github.com/jay0lee/GAM/" target="_blank">here</a> - they have an excellent Wiki section with detailed instructions on how to get GAM setup and running within your environment.</p><p>In subsequent blog posts I'll be covering some of the scripts/commands that I use to manage and bring some control over Google Classroom such as identifying active classes, exporting them to CSV, archiving (disabling) old classes that are no longer required, synchronising members (teachers and students) and managing/maintaining the owners of Classrooms.</p><p>I'll be executing GAM commands from within powershell to do this.</p><p>What tool do you use for managing Google Classroom? Do you wish Google would create some kind of web/graphical user interface to do this?</p><p><br /></p>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-10798670396257174772021-05-10T08:00:00.007+10:002021-05-10T08:00:00.175+10:00Automatically Disable Expired Accounts in Active Directory with Powershell<p>Active Directory has the ability to set an expiration date on accounts so that the account becomes inactive and can't be accessed/logged into once this date has passed. The problem with the way this works, is that technically the account is still "enabled" - as it's not actually "disabled" - it's simply expired. Disabled accounts are easily identified within Active Directory Users & Computers by a slightly different icon next to the account name. Expired accounts however, do not have any visual indication that they are expired, making them harder to identify.</p><p>I have created a powershell script which I run on a daily basis to automatically identify, and then disable any Active Directory accounts that have expired.</p><div style="text-align: left;"><div><span style="color: #04ff00; font-family: courier;">$report = @()</span></div><div><span style="color: #04ff00; font-family: courier;">$expiredusers = get-aduser -Filter * -Properties AccountExpirationDate | where {$_.AccountExpirationDate -lt (get-date) -and $_.AccountExpirationDate -ne $null -and $_.enabled -eq $true}</span></div><div><br /></div><div><div><span style="color: #04ff00; font-family: courier;">if ($expiredusers)</span></div><div><span style="color: #04ff00; font-family: courier;"> {</span></div><div><span style="color: #04ff00; font-family: courier;"> set-aduser -identity $($user.samaccountname) -enabled $false -description $newdesc</span></div><div><span style="color: #04ff00; font-family: courier;"> $report += new-object psobject -property @{Username=$($user.userprincipalname);AccountExpirationDate=$($user.accountexpirationdate);DN=$($user.distinguishedname)}</span></div><div><span style="color: #04ff00; font-family: courier;"> }</span></div></div><div>The $report = @() line creates a new/blank array that we will use later on to store the results of any expired </div><div><br /></div><div>Next we perform a search using the get-aduser cmdlet to search all Active Directory accounts for those with an expiration date prior (less than) the current date and whose current account status is Enabled</div><div><br /></div><div>An If statement is then run if any expired accounts are found which disables the account, and adds the account into the $report variable/array we declared earlier.</div><div><br /></div><div>You can then export the $report variable to a CSV file, email it to yourself etc.</div></div>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-63773526857969555062021-05-07T08:00:00.006+10:002021-08-04T15:19:18.508+10:00Connect to Office365/O365 using Powershell<p>A quick an easy post with details on how to connect your Office 365 (O365) environment to allow management with powershell cmdlets</p><p>Make sure you change the value next to -Credential from username@domain.com to the username you need to use to connect</p><div style="text-align: left;"><span style="color: #04ff00; font-family: courier;">$ex = New-PSSession -ConfigurationName Microsoft.Exchange -Credential username@domain.com -ConnectionUri https://outlook.office365.com/powershell -Authentication basic -AllowRedirection</span></div><div style="text-align: left;"><span style="color: #04ff00; font-family: courier;"><br /></span><span style="color: #04ff00; font-family: courier;">import-pssession $ex</span></div><div style="text-align: left;"><span style="color: #04ff00; font-family: courier;"><br /></span></div><div style="text-align: left;"><span style="font-family: inherit;">After executing this command, a popup window will appear for you to enter your password into</span></div><div style="text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsfZXrrhCfVhsC9tmtyDZlK2-Q4VtxJxvu00G9R6djjXVCz0bkC2HE2CeChAqTZ9sCXBzbhnp2USwCitEbQV8GSPoiE-jQCD1JFzpTNB8jZP5xbeybvD6tTTz0T4pGqaIMkv7zKK69kNP_/s322/2021-05-04+12_57_14-oakvs-scripts+-+Remote+Desktop+Connection.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="255" data-original-width="322" height="253" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsfZXrrhCfVhsC9tmtyDZlK2-Q4VtxJxvu00G9R6djjXVCz0bkC2HE2CeChAqTZ9sCXBzbhnp2USwCitEbQV8GSPoiE-jQCD1JFzpTNB8jZP5xbeybvD6tTTz0T4pGqaIMkv7zKK69kNP_/w320-h253/2021-05-04+12_57_14-oakvs-scripts+-+Remote+Desktop+Connection.png" width="320" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: left;"><br /></div>You will then receive a couple of warning messages to indicate that your session has been redirected to a slightly different URL, and another about some of the imported commands. These can be safely ignored<div><br /></div><div><div><span style="color: #ffa400; font-family: courier; font-size: x-small;">WARNING: Your connection has been redirected to the following URI: "https://outlook.office365.com/PowerShell-LiveID?PSVersion=5.1.17763.1852 "</span></div><div><span style="color: #ffa400; font-family: courier; font-size: x-small;">WARNING: The names of some imported commands from the module 'tmp_4kculvlx.2wc' include unapproved verbs that might make them less discoverable. To find the commands with unapproved verbs, run the Import-Module command again with the Verbose parameter. For a</span></div><div><span style="color: #ffa400; font-family: courier; font-size: x-small;"> list of approved verbs, type Get-Verb.</span></div><div style="text-align: left;"><br />You can then proceed to manage your Office 365 environment with the standard powershell commands - get-mailbox etc.</div><div style="text-align: left;"><span style="color: #04ff00; font-family: courier;"><br /></span></div><div style="text-align: left;"><span style="color: #04ff00; font-family: courier;"><br /></span></div></div>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-43459139452680664522021-05-06T08:00:00.001+10:002021-05-06T08:00:00.192+10:00Monitoring Hyper-V to Azure Site Replication with Powershell<p>Here is a simple script that I use to monitor the replication state of Hyper-V servers that I have replicated to Azure using Site Replication.</p><p>I have this script run every morning as a scheduled task which sends me an email with a nicely formatted table showing the replication status of all of my Hyper-V VM's to Azure</p><p>I have 4 Hyper-V nodes in a cluster - I have specified the 4 node names individually and used a ForEach loop to cycle through them all the generate the report. You can remove the loop if you're only using this for a single server/host.</p><p>(You will need to have the Hyper-V powershell module/cmdlets installed for this to work)</p><div style="text-align: left;"><div><span style="color: #04ff00; font-family: courier;">import-module Hyper-V -RequiredVersion 1.1</span></div><div><span style="color: #04ff00; font-family: courier;">$repstate = @()</span></div><div><span style="color: #04ff00; font-family: courier;">$date = get-date -f "dd-MM-yyyy"</span></div><div><span style="color: #04ff00; font-family: courier;">$clusternodes = "node1", "node2", "node3", "node4"</span></div><div><span style="color: #04ff00; font-family: courier;"><br /></span></div><div><span style="color: #04ff00; font-family: courier;">foreach ($node in $clusternodes)</span></div><div><span style="color: #04ff00; font-family: courier;"> {</span></div><div><span style="color: #04ff00; font-family: courier;"> $repstate += get-vmreplication -computername $node | select Name, State, Health, Mode, FrequencySec, PrimaryServer, ReplicaServer, PrimaryServerName, LastReplicationTime</span></div><div><span style="color: #04ff00; font-family: courier;"> }</span><br /><br /><span style="font-family: inherit;">You can then export the values from the $repstate variable to a CSV file and attach it, or as I have done, convert it to HTML and set it to the body of an email message</span></div><div><span style="font-family: inherit;"><br /></span></div><div><div><span style="color: #04ff00; font-family: courier;">$mailmessage.Body = $repstate | ConvertTo-HTML -fragment</span></div><div style="font-family: inherit;"><br /></div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUJh3TEUpPqCUfoGIgWaSMmaEWDTgAPjYWTyBkcPwPKlJrC396jd77FnQAMSQLLzxHLOmYx9K5C448KEfLKeBettobxGk7LgTJNI4LiAPiAig-cPeDXFoRw4zP1b31M4yI802lCBr9qJso/s955/2021-05-03+15_18_24-Hyper-V+to+Azure+Site+Replication+Report+-+03-05-2021+-+Message+%2528HTML%2529.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="448" data-original-width="955" height="301" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUJh3TEUpPqCUfoGIgWaSMmaEWDTgAPjYWTyBkcPwPKlJrC396jd77FnQAMSQLLzxHLOmYx9K5C448KEfLKeBettobxGk7LgTJNI4LiAPiAig-cPeDXFoRw4zP1b31M4yI802lCBr9qJso/w640-h301/2021-05-03+15_18_24-Hyper-V+to+Azure+Site+Replication+Report+-+03-05-2021+-+Message+%2528HTML%2529.png" width="640" /></a></div><br /><div style="font-family: inherit;"><br /></div></div>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-82091899532055160512021-05-05T08:00:00.006+10:002021-05-06T15:59:18.454+10:00Storing and Using Encrypted Passwords with Powershell<p>There are lots of reasons why a username and password may be incorporated into a Powershell script - naturally, the quickest and easiest way to do this is to simply store the username and password in a script using a couple of variables such as;</p><p><span style="color: #04ff00; font-family: courier;">$username = "user1"<br />$password = "password1"</span></p><p>Simple, and it works, but is definitely not secure.</p><p>There is a simple method you can use though to encrypt the password using powershell and store it as a text file on your computer. The password is encrypted using the currently logged in windows account that is running the powershell script (when you encrypt it) so keep this in mind if you are trying to decrypt the password with a different user account (because it won't work).</p><p>This is of course a 2 step process. The first step is to take your unsecured password (yourpassword) then encrypt it, and store the encrypted text value into a txt file (in C:\temp\user1-secpw.txt)</p><div style="text-align: left;"><div><span style="color: #04ff00; font-family: courier;">#Store Secure Credentials </span></div><div><span style="color: #04ff00; font-family: courier;">$unsecpw = "yourpassword"</span></div><div><span style="color: #04ff00; font-family: courier;">$secpw = $unsecpw | convertto-securestring -asplaintext -force</span></div><div><span style="color: #04ff00; font-family: courier;">$securestringtext = $secpw | convertfrom-securestring</span></div><div><span style="color: #04ff00; font-family: courier;">set-content "C:\temp\user1-secpw.txt" $securestringtext</span></div><div><br /></div><div>Once you've done this part and you have your password stored in the txt file, you should at the very least clear the value from the $unsecpw if you are saving this script - otherwise you're essentially defeating the purpose of encrypting it in the first place.</div><div><br /></div><div>The second part is of course the process of importing the txt file into a new script so you can use the password. As previously mentioned, you will only be able to decrypt the password if you are executing the decryption commands under the same user security context (ie. windows user) as when you encrypted it.</div><div><br /></div><div><span style="color: #04ff00; font-family: courier;">$pwd = get-content "C:\temp\user1-secpw.txt" | convertto-securestring</span></div><div><span style="color: #04ff00; font-family: courier;">$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pwd)</span></div><div><span style="color: #04ff00; font-family: courier;">$Unsecpw = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)</span><br /><br /><span style="font-family: inherit;">Your password is now available to use via the $unsecpw variable</span></div></div>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0tag:blogger.com,1999:blog-6779289165196646900.post-17645191089878668512021-05-04T08:00:00.001+10:002021-05-04T08:00:00.246+10:00Reading Emails from O365 Mailbox with Powershell<p>Here is a script you can use to connect to an Office 365 (O365) mailbox to read/parse email messages. After the message has been read it is then moved to a specific folder within the mailbox. The full script is included first, and I will then break it down section by section afterwards</p><div style="text-align: left;"><span style="color: #04ff00; font-family: courier;">#Enable Exchange/O365 cmdlets<br />add-pssnapin *exchange* -erroraction SilentlyContinue<br />Import-Module MSOnline -ErrorAction SilentlyContinue<br /></span></div><div style="text-align: left;"><span style="color: #04ff00; font-family: courier;"><br /></span></div><div style="text-align: left;"><span style="color: #04ff00; font-family: courier;">#Credentials</span></div><div style="text-align: left;"><span style="color: #04ff00; font-family: courier;">$email = email@domain.com</span></div><div style="text-align: left;"><span style="color: #04ff00; font-family: courier;">$password = "password"</span></div><div style="text-align: left;"><span style="color: #04ff00; font-family: courier;">$emaildomain = domain.com</span></div><p><span style="color: #04ff00; font-family: courier;">#Connect to Mailbox<br />[void][Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll")<br />$s = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1)<br />$s.Credentials = New-Object Net.NetworkCredential($email, $password, $emaildomain)<br />$s.Url = new-object Uri("https://outlook.office365.com/EWS/Exchange.asmx");<br />$inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($s,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)</span></p><p><span style="color: #04ff00; font-family: courier;">#Setup folder and folder view (to move completed messages to)<br />$fv = new-object Microsoft.Exchange.WebServices.Data.FolderView(30)<br />$fv.Traversal = "Deep"<br />$folders = $s.findFolders([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$fv)<br />$processedfolder = $folders | where {$_.displayname -eq "ProcessedEmails"}</span></p><p><span style="color: #04ff00; font-family: courier;">#Get unread messages<br />$msgs = $null<br />$iv = new-object Microsoft.Exchange.WebServices.Data.ItemView(50)<br />$inboxfilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And)<br />$ifisread = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::IsRead,$false)<br />$inboxfilter.add($ifisread)<br />$msgs = $s.FindItems($inbox.Id, $inboxfilter, $iv)</span></p><p><span style="color: #04ff00; font-family: courier;">#If unread message(s) found, do stuff<br /></span><span style="color: #04ff00; font-family: courier;">if ($msgs.totalcount -ne 0)<br /></span><span style="color: #04ff00; font-family: courier;"> {<br /></span><span style="color: #04ff00; font-family: courier;"> $psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)<br /></span><span style="color: #04ff00; font-family: courier;"> $psPropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text;<br /></span><span style="color: #04ff00; font-family: courier;"> $s.LoadPropertiesForItems($msgs,$psPropertySet)<br /></span><span style="color: #04ff00; font-family: courier;"> $msgs = $msgs.items<br /></span><span style="color: #04ff00; font-family: courier;"><span> </span>}<br /></span><br /><span style="color: #04ff00; font-family: courier;">#Move Message to Processed Folder</span><br /><span style="color: #04ff00; font-family: courier;">$msg.Move($processedfolder.Id)</span></p><div><span style="color: #04ff00; font-family: courier;"><div><br /></div></span></div><div style="text-align: left;">The #Enable Exchange/O365 Cmdlets section as the comment suggests is to enable the Microsoft Exchange & Office 365 cmdlets</div><p style="text-align: left;"><span style="color: #04ff00; font-family: courier;">add-pssnapin *exchange* -erroraction SilentlyContinue</span><br style="color: #04ff00; font-family: courier;" /><span style="color: #04ff00; font-family: courier;">Import-Module MSOnline -ErrorAction SilentlyContinue</span></p><p style="text-align: left;">The #Credentials section is where you enter the details (username, password and email domain) used to connect to the mailbox</p><div><div><span style="color: #04ff00; font-family: courier;">#Credentials</span></div><div><span style="color: #04ff00; font-family: courier;">$email = email@domain.com</span></div><div><span style="color: #04ff00; font-family: courier;">$password = "password"</span></div><div><span style="color: #04ff00; font-family: courier;">$emaildomain = domain.com</span></div></div><div><span style="color: #04ff00; font-family: courier;"><br /></span></div><div>Next, we will connect to the mailbox (and look at the Inbox folder specifically), using the credentials specified above and Microsoft Exchange Web Services. If you don't already have the Exchange Web Services files installed you will need to install them</div><p><span style="color: #04ff00; font-family: courier;">#Connect to Mailbox</span><br style="color: #04ff00; font-family: courier;" /><span style="color: #04ff00; font-family: courier;">[void][Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll")</span><br style="color: #04ff00; font-family: courier;" /><span style="color: #04ff00; font-family: courier;">$s = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1)</span><br style="color: #04ff00; font-family: courier;" /><span style="color: #04ff00; font-family: courier;">$s.Credentials = New-Object Net.NetworkCredential($email, $password, $emaildomain)</span><br style="color: #04ff00; font-family: courier;" /><span style="color: #04ff00; font-family: courier;">$s.Url = new-object Uri("https://outlook.office365.com/EWS/Exchange.asmx");</span><br style="color: #04ff00; font-family: courier;" /><span style="color: #04ff00; font-family: courier;">$inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($s,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)</span></p><p>Now, a folder view is setup to obtain the list of all the folders in the mailbox. All the list of available folders are stored in the $folders variable. We then look for a folder called "ProcessedTickets" and set this folder to be the $processedfolder variable. We use this variable (folder) later on to move processed emails into. You can of course change this folder name to be anything you like.</p><p><span style="color: #04ff00; font-family: courier;">#Setup folder and folder view (to move completed messages to)</span><br style="color: #04ff00; font-family: courier;" /><span style="color: #04ff00; font-family: courier;">$fv = new-object Microsoft.Exchange.WebServices.Data.FolderView(30)</span><br style="color: #04ff00; font-family: courier;" /><span style="color: #04ff00; font-family: courier;">$fv.Traversal = "Deep"</span><br style="color: #04ff00; font-family: courier;" /><span style="color: #04ff00; font-family: courier;">$folders = $s.findFolders([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$fv)</span><br style="color: #04ff00; font-family: courier;" /><span style="color: #04ff00; font-family: courier;">$processedfolder = $folders | where {$_.displayname -eq "ProcessedEmails"}</span></p><p>Now we will retrieve any unread messages within the mailbox - filters are set using the $inboxfilter and $ifisread variables. All messages matching the search criteria are stored in the $msgs variable</p><p><span style="color: #04ff00; font-family: courier;">#Get unread messages<br /></span><span style="color: #04ff00; font-family: courier;">$msgs = $null<br /></span><span style="color: #04ff00; font-family: courier;">$iv = new-object Microsoft.Exchange.WebServices.Data.ItemView(50)<br /></span><span style="color: #04ff00; font-family: courier;">$inboxfilter = new-object <br />Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And)<br /></span><span style="color: #04ff00; font-family: courier;">$ifisread = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::IsRead,$false)<br /></span><span style="color: #04ff00; font-family: courier;">$inboxfilter.add($ifisread)<br /></span><span style="color: #04ff00; font-family: courier;">$msgs = $s.FindItems($inbox.Id, $inboxfilter, $iv)</span></p><p>If messages are found in the $msgs variable, the properties for those messages are then loaded back into the $msgs variable<br /><br /></p><div style="text-align: left;"><span style="color: #04ff00; font-family: courier;">#If unread message(s) found, do stuff<br /></span><span style="color: #04ff00; font-family: courier;">if ($msgs.totalcount -ne 0)<br /></span><span style="color: #04ff00; font-family: courier;"> {<br /></span><span style="color: #04ff00; font-family: courier;"> $psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)<br /></span><span style="color: #04ff00; font-family: courier;"> $psPropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text;<br /></span><span style="color: #04ff00; font-family: courier;"> $s.LoadPropertiesForItems($msgs,$psPropertySet)<br /></span><span style="color: #04ff00; font-family: courier;"> $msgs = $msgs.items<br /></span><span style="color: #04ff00; font-family: courier;"> }</span></div><br />If you look at the $msgs variable at this point, it should contain the properties of all mail messages found in the inbox that are unread. You can then use a foreach loop to cycle through each message and action accordingly.<p></p><p>Properties such as subject, body text, from address are all available and can be used to further filter entries</p><p>Once you've actioned the email and done what you need, you can then move the email into the processedfolders folder we previously specified</p><p><span style="color: #04ff00; font-family: courier;">#Move Message to Processed Folder</span><br /><span style="color: #04ff00; font-family: courier;">$msg.Move($processedfolder.Id)</span></p><div><span style="color: #04ff00; font-family: courier;"><br /></span></div>Peter Morrisseyhttp://www.blogger.com/profile/03709106060725418091noreply@blogger.com0