tag:blogger.com,1999:blog-61591863164975893962024-03-27T01:37:54.406-05:00Mick's IT BlogsMy blog is here to help solve issues I have encountered and solved, publish scripts I have written, and educate others. Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.comBlogger370125tag:blogger.com,1999:blog-6159186316497589396.post-70174005409222096762023-04-07T10:05:00.001-05:002023-04-07T10:05:16.278-05:00Trigger Windows Update in ConfigMgr without PowerShell<div><span style="background-color: #f7f7f8; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; white-space: pre-wrap;">I've been meaning to share this blog post for some time now, and finally got around to it. If you need to trigger a Windows update without using PowerShell, there is a way to do it. In newer versions of Windows 10, usoclient.exe is used to initiate an update scan. However, most of its parameters no longer work except for startinteractivescan. The issue I faced was that it had to be executed as the system account or as the end user in ConfigMgr. Although ConfigMgr runs installations with the system account, it did not work with usoclient.exe. The solution was to use psexec.exe with the -s parameter to trigger it. I ran it as a task sequence, but you can also execute it under a package. I had already pushed psexec.exe to all systems in my environment, but you may need to include it in the package and run it from there. Don't forget to use the -accepteula parameter; otherwise, it will hang if it has never been executed on a machine before.</span></div><div><span style="background-color: #f7f7f8; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; white-space: pre-wrap;"><br /></span></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4ovEFMc6yLdo1esFtoPlMnlvjeigVachh9BDu5drXFfjLhfG8eJ2DQ9u9IPJVIkH3nluKAHmNl0X-EYpFbTrD7TWBc-FMRY256_tuDnJIt9ZgL0TAIn5X77R0aFBIdsmypSlfi81BaTWELd8qwbjOSPrHdC4rYUA8ytw9m1AZsC5Brge0GV4O1HQU/s930/WindowsUpdate.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="930" data-original-width="817" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4ovEFMc6yLdo1esFtoPlMnlvjeigVachh9BDu5drXFfjLhfG8eJ2DQ9u9IPJVIkH3nluKAHmNl0X-EYpFbTrD7TWBc-FMRY256_tuDnJIt9ZgL0TAIn5X77R0aFBIdsmypSlfi81BaTWELd8qwbjOSPrHdC4rYUA8ytw9m1AZsC5Brge0GV4O1HQU/w562-h640/WindowsUpdate.JPG" width="562" /></a></div><br /><div><span style="background-color: #f7f7f8; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; white-space: pre-wrap;">If you observe the Check for Updates window while executing the package in Software Center, you can see it initiate the update scan, as demonstrated below.</span></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4v4ceRQZWi7cSzwDWawx2AA9qH34yMjWS_O6Kqzn9AlClMzpWuY7lp4WErcs6w5ZHzdI_I58WEDidBIfVI7-IakCZH9R8PixFgk52Cl713g26lqt8wFfXsFtkG-9bKwKMEEkVW20XmeYFje8Xc4HO831XK9fZ_irThFAsQ13KDqVOL41REVhk-IL2/s625/TriggerUpdate.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="625" data-original-width="505" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4v4ceRQZWi7cSzwDWawx2AA9qH34yMjWS_O6Kqzn9AlClMzpWuY7lp4WErcs6w5ZHzdI_I58WEDidBIfVI7-IakCZH9R8PixFgk52Cl713g26lqt8wFfXsFtkG-9bKwKMEEkVW20XmeYFje8Xc4HO831XK9fZ_irThFAsQ13KDqVOL41REVhk-IL2/w518-h640/TriggerUpdate.JPG" width="518" /></a></div><br /><div><br /></div>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-62731969152415602592022-09-06T13:32:00.003-05:002022-09-06T13:32:52.615-05:00Imaging using MDT with Autopilot<p>If you have a system that has been infected or the drive had to be replaced, you'll need to lay down a new bare OS and then have autopilot finish up the configuration of the machine. For this, we are using MDT as it is easy to maintain and very fast at imaging. </p><p>I used <a href="https://osddeployment.dk/2018/12/08/how-to-deploy-autopilot-device-fast-with-mdt/" target="_blank">this</a> as a template to come up with the solution we are now using. The first thing I did was to use everything verbatim from the section of the blog named "How to get the Windows Autopilot payload". It is the next section How to customize the MDT Task Sequence for Autopilot that I diverged from. I could not get that section to work. The main problem was the task sequence. The blog says to use a custom task sequence when in reality, you must use a Standard Client Task Sequence. </p><p>The first thing I did was to disable Postinstall and State Restore groups as we will not be joining a domain and the task sequence needs to stop in Postinstall mode. The next thing was to create a new Postinstall group as shown below. </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBpm0RK8gfypCeSNJGVDvcMk8kHW0gPN8A0Uc20MMuLf5jUYBmuKOuaUPON4B2R3fPyYdihyq-un5t1919pgnuKPE2ht3yfbk5mqfRY-V2hQnflFtiOqCnKaRBEr1Tcj2cpgpnd7ZoXKfvb6xjzmdQ5EJfeCbl00sDi8mISlUkWLPY7xDmVg48vAyS/s714/mdt01.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="655" data-original-width="714" height="589" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBpm0RK8gfypCeSNJGVDvcMk8kHW0gPN8A0Uc20MMuLf5jUYBmuKOuaUPON4B2R3fPyYdihyq-un5t1919pgnuKPE2ht3yfbk5mqfRY-V2hQnflFtiOqCnKaRBEr1Tcj2cpgpnd7ZoXKfvb6xjzmdQ5EJfeCbl00sDi8mISlUkWLPY7xDmVg48vAyS/w640-h589/mdt01.JPG" width="640" /></a></div><br /><p>The next thing was to create the CustomSettings_Autopilot.ini as described in the referring blog. The Gather uses the CustomSettings_Autopilot.ini file as described in the blog. </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFtV1b-EURTRQlz4CjydQ1jdmM-5F8BPoDLXBrO867T4UJTnVCf6BtOejnigJa4ytXSJwafzH_5d7Iz4JxRBYdOsF3KDCj70b4NSQ1lGbhNv17N0sssHPREPCFMi-MPkcsBqjHBtJpFDJ9t0XehVYqLBwcwsxAPDplTX3V4IS3ey5uQRblcblJsuFN/s683/mdt02.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="390" data-original-width="683" height="366" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFtV1b-EURTRQlz4CjydQ1jdmM-5F8BPoDLXBrO867T4UJTnVCf6BtOejnigJa4ytXSJwafzH_5d7Iz4JxRBYdOsF3KDCj70b4NSQ1lGbhNv17N0sssHPREPCFMi-MPkcsBqjHBtJpFDJ9t0XehVYqLBwcwsxAPDplTX3V4IS3ey5uQRblcblJsuFN/w640-h366/mdt02.JPG" width="640" /></a></div><br /><p><br /></p><p>The Apply Autopilot Profile also uses what is described in the other blog. </p><p></p><ul style="text-align: left;"><li>xcopy %SCRIPTROOT%\AutopilotConfigurationFile.json %OSDisk%\Windows\provisioning\AutoPilot\ /c</li></ul><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHnCnp-rglQOdv8vEAGy1UaaZqeK-FD7KWVaDmF62MKvMebzxDJdLE6PjIUuAq4saex0pB4RRHKmW_BDtm9XydSHBVey1zoA6YdwGGl2NRvHIrBArLJspf278YnwjqKzKFCFXd4Wbz7xSu2dH0SIhclhClUK-8en_m9BdOm8L1J5Ig_znW5_erQ8Ol/s679/mdt03.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="459" data-original-width="679" height="432" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHnCnp-rglQOdv8vEAGy1UaaZqeK-FD7KWVaDmF62MKvMebzxDJdLE6PjIUuAq4saex0pB4RRHKmW_BDtm9XydSHBVey1zoA6YdwGGl2NRvHIrBArLJspf278YnwjqKzKFCFXd4Wbz7xSu2dH0SIhclhClUK-8en_m9BdOm8L1J5Ig_znW5_erQ8Ol/w640-h432/mdt03.JPG" width="640" /></a></div><br /><div>The next stip is to delete the unattend.xml file as shown below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiva3I8gKQh5wDn1JaxXEx8G-JuuF7Dp0KeJmLa46LW5oSWFBg-9aj05h11r9zcwel4xm4ZaiZb6npXGPesWcOC2WwnMxm91NLfjiF50oyOgSa02IXgcNzC7V28NvBzrm4NFr1ec-_80lZaZ5ZgLUc86vTAsFvJH7OWvgUQ3Xff-tZxEzvIVvW3bIwu/s688/mdt04.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="490" data-original-width="688" height="456" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiva3I8gKQh5wDn1JaxXEx8G-JuuF7Dp0KeJmLa46LW5oSWFBg-9aj05h11r9zcwel4xm4ZaiZb6npXGPesWcOC2WwnMxm91NLfjiF50oyOgSa02IXgcNzC7V28NvBzrm4NFr1ec-_80lZaZ5ZgLUc86vTAsFvJH7OWvgUQ3Xff-tZxEzvIVvW3bIwu/w640-h456/mdt04.JPG" width="640" /></a></div><br /><div>The next task places a file named autopilot.txt in the root directory of the c: drive. This is a flag to run the Intune package that will delete all MDT associated files and registry keys to terminate the MDT build. </div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPHd6VBwq_TyEMHdvxyo4HJapGzgrdsT1cjeJjSgbNKdnMAP2qqjMFYYTeHYD3ERc9eakE59EV4sBDpk-IZdG7Qa0tfw3I7erQpBQt5kHppOaiM4zzn3H5tud13d3PloGkl8ct1SUOKweyRQjrHtbfO9Y1d1oBv6UbzsGvrYW81R7iysSw_Er2HoZ5/s678/mdt06.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="437" data-original-width="678" height="412" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPHd6VBwq_TyEMHdvxyo4HJapGzgrdsT1cjeJjSgbNKdnMAP2qqjMFYYTeHYD3ERc9eakE59EV4sBDpk-IZdG7Qa0tfw3I7erQpBQt5kHppOaiM4zzn3H5tud13d3PloGkl8ct1SUOKweyRQjrHtbfO9Y1d1oBv6UbzsGvrYW81R7iysSw_Er2HoZ5/w640-h412/mdt06.JPG" width="640" /></a></div><br /><div>The next task will delete most of the MDT files on the system. </div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhO9HcinqCCIHGwvPWg7YquXu58Q7a44w7hMP7ph58hCai-dNngqpauf2ZUFJVcIOf1ikMog6AFzLRUAGcLSl1gIQ2KEGGb9G_Jw9kSzHGBPp-Gmo8Ia0eEb12KzR5go5g7I6wZcjRb0uLallV-E7sM1sr3oY2avqEIuG_GGq1onfUlAxGJQcANNot1/s681/mdt07.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="457" data-original-width="681" height="430" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhO9HcinqCCIHGwvPWg7YquXu58Q7a44w7hMP7ph58hCai-dNngqpauf2ZUFJVcIOf1ikMog6AFzLRUAGcLSl1gIQ2KEGGb9G_Jw9kSzHGBPp-Gmo8Ia0eEb12KzR5go5g7I6wZcjRb0uLallV-E7sM1sr3oY2avqEIuG_GGq1onfUlAxGJQcANNot1/w640-h430/mdt07.JPG" width="640" /></a></div><br /><div>Finally, the Restart task reboots the system at which point it will come up to the Autopilot user signon page. </div><div><br /></div><div>As far as the SetupCompleteAutopilot.cmd file, here is what I have inside it. I changed some of the script to use environmental variables and I added the deletion of the c:\autopilot.txt file and the creation of the same file under c:\windows\temp to signify the script executed and the system has been cleaned up so the Intune package registers the execution as a success. </div><div><br /></div><p></p><div>@echo off</div><div><div>:: // ***************************************************************************</div><div>:: //</div><div>:: //</div><div>:: // File: SetupComplete.cmd</div><div>:: //</div><div>:: // Version: 1.0</div><div>:: //</div><div>:: // Purpose: Cleanup after MDT Autopilot deployment</div><div>:: //</div><div>:: // ***************************************************************************</div><div><br /></div><div>:: Copy to windows setup folder for application verification purposes in Intune</div><div>copy /V /Y \\prodfs01\All\ProdApps\Waller\SetupComplete\SetupCompleteAutopilot.cmd %WINDIR%\Setup\SetupCompleteAutopilot.cmd</div><div>:: Workaround for incorrectly-registered TS environment</div><div>reg delete HKCR\Microsoft.SMS.TSEnvironment /f > nul 2>&1</div><div>rmdir /Q /S %OSDisk%\MININT </div><div>rmdir /Q /S %OSDisk%\_SMSTaskSequence</div><div>del /Q %OSDisk%\LTIBootstrap.vbs</div><div>del /Q %OSDisk%\autopilot.txt</div><div>echo Test > %WINDIR%\Temp\autopilot.txt</div></div><div><br /></div><div><br /></div><div>In Intune, I created the app called MDT Cleanup. Here is the program page. I used a dummy delete.cmd file as that part is not needed. </div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9QlJsVhed71q-oYZ9zRgqkMb52QDnF2jmPDmpq5ftQANpGtdI-memtWsukLA8Ni5aBeYeNdNZqqmyDu5qexi5zrdsqSQPUpYoNiHhJTV7MFf_u6hW2l_fOORbr_ROxP4zuWy9tdZ9OoKC0y82lpaSTo9vny01N2aC2gmIfPbqwXIm6N-LFfRHKhXj/s782/intune01.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="595" data-original-width="782" height="486" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9QlJsVhed71q-oYZ9zRgqkMb52QDnF2jmPDmpq5ftQANpGtdI-memtWsukLA8Ni5aBeYeNdNZqqmyDu5qexi5zrdsqSQPUpYoNiHhJTV7MFf_u6hW2l_fOORbr_ROxP4zuWy9tdZ9OoKC0y82lpaSTo9vny01N2aC2gmIfPbqwXIm6N-LFfRHKhXj/w640-h486/intune01.JPG" width="640" /></a></div><div><br /></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_VWW0g-VotkH5SxFHPbIAwXaauT6u8gnJDkpRCzeyFFaNUcibKHCUtJxEoz-dBQt4AJ1zuX4XYXiQogmkxS_ULCItEB4IbmRYO88nQ8LL8sKgd-PaP4tdKNDPIlEPOWSpLdDiouNpN5dDr3BJEg7jI6IkUCEkuA_WSd2GPbp-JMWXoEe0PyRhucMx/s1262/intune03.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="591" data-original-width="1262" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_VWW0g-VotkH5SxFHPbIAwXaauT6u8gnJDkpRCzeyFFaNUcibKHCUtJxEoz-dBQt4AJ1zuX4XYXiQogmkxS_ULCItEB4IbmRYO88nQ8LL8sKgd-PaP4tdKNDPIlEPOWSpLdDiouNpN5dDr3BJEg7jI6IkUCEkuA_WSd2GPbp-JMWXoEe0PyRhucMx/w640-h300/intune03.JPG" width="640" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgssR_Vxr4AI8OCTd6_UKmFgBIqiLV2Xhabz5-dfRV5SpX7OBXqMmSwZrKnU122EO2SI2-Jm2QxvxlB8tULHVreQIDeSiPxEfRrEe9WFCO3OgugJvvOa154X1iFuZUo57647lxWYNDYdDxMXQpbPjyiYr7C8bB3OABxErRMnWDA9jib0KcJ99-qmPBG/s1220/intune04.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="367" data-original-width="1220" height="192" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgssR_Vxr4AI8OCTd6_UKmFgBIqiLV2Xhabz5-dfRV5SpX7OBXqMmSwZrKnU122EO2SI2-Jm2QxvxlB8tULHVreQIDeSiPxEfRrEe9WFCO3OgugJvvOa154X1iFuZUo57647lxWYNDYdDxMXQpbPjyiYr7C8bB3OABxErRMnWDA9jib0KcJ99-qmPBG/w640-h192/intune04.JPG" width="640" /></a></div><br /><div>That is all that is to the Intune package. Now the system will build a bare-bones OS and then transfer the rest of the build process to Intune. </div>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-35971427044439432202022-08-03T12:41:00.002-05:002022-08-03T12:41:33.381-05:00Find Programs and Features Uninstall Registry Keys with PowerShell<p> I am working on a new package to upgrade one of the applications. This time, it requires I uninstall the old app first before installing the new version. There are two different versions, so I needed to retrieve the uninstall strings for both. That is when I decided to write this script that will scan the registry for the application and list the key values as shown below. It scans both the x86 and x64 Uninstall registry entries. The script was also written to be able to scan for wildcard values if there is more than one entry you are looking for. </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtUkztk9fSDQKCd5Jy3hZ5hGkeDVm-MBWWPUHXQ1tX29d62TMhM_3y9017o_S2D3C9s--zLFX3zO3047P7K8ZWG6MaqxixeC4MmdowtrPD3ezJ1ZtzMEWn9CYRiCTlUanyYGGCIs_9luHamUEVTyOPyKEkjOnpYgMLcfIBPNe5Nb2arUh3-Je2v_d_/s965/Capture.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="479" data-original-width="965" height="318" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtUkztk9fSDQKCd5Jy3hZ5hGkeDVm-MBWWPUHXQ1tX29d62TMhM_3y9017o_S2D3C9s--zLFX3zO3047P7K8ZWG6MaqxixeC4MmdowtrPD3ezJ1ZtzMEWn9CYRiCTlUanyYGGCIs_9luHamUEVTyOPyKEkjOnpYgMLcfIBPNe5Nb2arUh3-Je2v_d_/w640-h318/Capture.JPG" width="640" /></a></div><br /><p><br /></p><p>You can download the script from my <a href="https://github.com/MicksITBlogs/PowerShell/blob/master/FindRegistryUninstall.ps1" target="_blank">GitHub</a>.</p><p><br /></p>
<pre style="background-attachment: initial; background-clip: initial; background-color: #f0f0f0; background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz1xGp9q86dTZmBT6Kgf4lTiCRnpB-883UDue4w0nS25otuiDMzqANNGCcUIMXUyHVEm0-djravLr2rh4_OiSiUlcv2KthEqRgIz1dikIsp_zoYcmSlQyrulsFHeMkbNa6Gd9iCnqBnO-K/s320/codebg.gif); background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> <#
.SYNOPSIS
Uninstall Finder
.DESCRIPTION
This script will retrieve the x86 and x64 uninstall registry key(s) for a specific applicaton. This is very helpful for Configuration Manager admins when needing to create packages, especially uninstall packages.
.PARAMETER ApplicationName
Name of the application as it appears in Add/Remove Programs
.PARAMETER Like
Select this if using a partial name or wanting multiple listings to appear
.NOTES
===========================================================================
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2022 v5.8.209
Created on: 8/3/2022 11:44 AM
Created by: Mick Pletcher
Filename: FindRegistryUninstall.ps1
===========================================================================
#>
[CmdletBinding()]
param
(
[ValidateNotNullOrEmpty()]
[string]$ApplicationName,
[switch]$Like
)
If ($Like.IsPresent) {
Get-ChildItem -Path REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall | Get-ItemProperty | Where-Object { $_.DisplayName -like ('*' + $ApplicationName + '*') }
} else {
Get-ChildItem -Path REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall | Get-ItemProperty | Where-Object { $_.DisplayName -eq $ApplicationName }
}
</code></pre>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-70865465922662626892022-08-02T14:10:00.004-05:002022-08-02T14:10:55.269-05:00Configuring Wake-On-LAN for Dell SystemsThe firm I am at has recently upgraded all systems to the newest model Dells. In doing so, some of the settings for configuring WOL have changed in the BIOS. I have rewritten this script to cover all changes to the OS, BIOS, and NIC. The script uses the DellSMBios PowerShell module to configure the BIOS settings. Thanks to these sites for pertinent information that helped with writing this tool:<div><ul style="text-align: left;"><li><a href="https://www.dell.com/support/kbdoc/en-us/000175283/how-to-setup-wake-on-lan-wol-on-your-dell-system" target="_blank">How to setup Wake on LAN on your Dell System</a><br /></li><li><a href="https://www.isumsoft.com/windows-10/turn-on-off-fast-startup-in-windows-10.html" target="_blank">How to Turn on/off Fast Startup in Windows 10</a><br /></li><li><a href="https://docs.microsoft.com/en-us/troubleshoot/windows-client/networking/power-management-on-network-adapter" target="_blank">Information about power management setting on a network adapter</a><br /></li></ul><div>And yes, I do realize I could have consolidated some of the code into a function that would have worked repetitively against each setting. </div><div><br /></div><div>The things that must be done to get this to work are:</div></div><div><ul style="text-align: left;"><li>BIOS: Disable CStatesCtrl</li><li>BIOS: Enable Wake-On-LAN either LanWlan or LanOnly</li><li>BIOS: Disable DeepSleepCtrl</li><li>BIOS: Disable BlockS3</li><li>NIC: Disable Energy Efficient Ethernet</li><li>NIC: Turn on Wake on Magic Packet</li><li>OS: Turn off Hibernation</li><li>OS: Enable Allow the computer to turn off this device is configured</li></ul><div>This is a screenshot of the script after it successfully ran against a Dell 7090 machine. </div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwE8MCra9L2RZPs0FL9jWPS2hmJDtpY0dpHeAam3d2TuqERnbPc1ONVacQ_mU6MSdQumfKqAyaUOkD1u1MZTF-XvvJMb8j_gE97oGDFVTPnWWpVNqm6rCNK2w94np-N-J_g8PtSrnnod4buH2gKDao8jLey_2XqEnW_pY3RmV0TsU6wtM3SLrvo3ZO/s299/Capture.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="183" data-original-width="299" height="245" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwE8MCra9L2RZPs0FL9jWPS2hmJDtpY0dpHeAam3d2TuqERnbPc1ONVacQ_mU6MSdQumfKqAyaUOkD1u1MZTF-XvvJMb8j_gE97oGDFVTPnWWpVNqm6rCNK2w94np-N-J_g8PtSrnnod4buH2gKDao8jLey_2XqEnW_pY3RmV0TsU6wtM3SLrvo3ZO/w400-h245/Capture.JPG" width="400" /></a></div><br /><div><br /></div><div><br /></div><div>Here is the script that I wrote and works in our environment on Dell Optiplexes and Latitudes. </div></div><div><br /></div><div>You can download it from my <a href="https://github.com/MicksITBlogs/PowerShell/blob/master/WOL.ps1" target="_blank">GitHub site</a>. </div><div><br /></div><div><br /></div>
<pre style="background-attachment: initial; background-clip: initial; background-color: #f0f0f0; background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz1xGp9q86dTZmBT6Kgf4lTiCRnpB-883UDue4w0nS25otuiDMzqANNGCcUIMXUyHVEm0-djravLr2rh4_OiSiUlcv2KthEqRgIz1dikIsp_zoYcmSlQyrulsFHeMkbNa6Gd9iCnqBnO-K/s320/codebg.gif); background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> <#
.SYNOPSIS
Wake-On-LAN
.DESCRIPTION
A description of the file.
.PARAMETER ConsoleTitle
Title for PowerShell console
.PARAMETER BIOSPassword
A description of the BIOSPassword parameter.
.NOTES
===========================================================================
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2022 v5.8.209
Created on: 8/1/2022 8:16 AM
Created by: Mick Pletcher
Filename: WOL.ps1
===========================================================================
#>
param
(
[ValidateNotNullOrEmpty()]
[string]$ConsoleTitle,
[string]$BIOSPassword
)
function Set-BIOS {
<#
.SYNOPSIS
Configure WOL in BIOS
.DESCRIPTION
Configure WOL in BIOS
#>
[CmdletBinding()]
param ()
#Import Dell BIOS Provider PowerShell Module
Try {
Import-Module -Name DellBIOSProvider
}
catch {
Find-Module -Name DellBIOSProvider | Install-Module -Force
Import-Module -Name DellBIOSProvider
}
#Set Wake-On-LAN to LanOnly
$BIOSItem = "PowerManagement\WakeOnLan"
$NewValue = "LanWlan"
#Check if LanWlan is available
If ($NewValue -notin ("DellSmBios:\" + $BIOSItem).PossibleValues) {
$NewValue = "LanOnly"
}
If (Get-Item -Path ("DellSmBios:\" + $BIOSItem) -ErrorAction SilentlyContinue) {
Write-Host ("BIOS" + [char]32 + $BIOSItem.split('\')[1] + ":" + [char]32) -NoNewline
If ((Get-Item -Path ("DellSmBios:\" + $BIOSItem)).CurrentValue -ne $NewValue) {
If ($BIOSPassword) {
Set-Item -Path ("DellSmBios:\" + $BIOSItem) -Value $NewValue -Force -Password $BIOSPassword
} else {
Set-Item -Path ("DellSmBios:\" + $BIOSItem) -Value $NewValue -Force
}
If ((Get-Item -Path ("DellSmBios:\" + $BIOSItem)).CurrentValue -eq $NewValue) {
Write-Host $NewValue -ForegroundColor Yellow
}
else {
Write-Host (Get-Item -Path ("DellSmBios:\" + $BIOSItem)).CurrentValue -ForegroundColor Red
}
}
else {
Write-Host $NewValue -ForegroundColor Yellow
}
}
#Disable CState Control
$BIOSItem = "Performance\CStatesCtrl"
$NewValue = "Disabled"
#Test if CState exists
If (Get-Item -Path ("DellSmBios:\" + $BIOSItem) -ErrorAction SilentlyContinue) {
Write-Host ("BIOS" + [char]32 + $BIOSItem.split('\')[1] + ":" + [char]32) -NoNewline
If ((Get-Item -Path ("DellSmBios:\" + $BIOSItem)).CurrentValue -ne $NewValue) {
If ($BIOSPassword) {
Set-Item -Path ("DellSmBios:\" + $BIOSItem) -Value $NewValue -Force -Password $BIOSPassword
}
else {
Set-Item -Path ("DellSmBios:\" + $BIOSItem) -Value $NewValue -Force
}
If ((Get-Item -Path ("DellSmBios:\" + $BIOSItem)).CurrentValue -eq $NewValue) {
Write-Host $NewValue -ForegroundColor Yellow
}
else {
Write-Host (Get-Item -Path ("DellSmBios:\" + $BIOSItem)).CurrentValue -ForegroundColor Red
}
}
else {
Write-Host $NewValue -ForegroundColor Yellow
}
}
#Disable Deep Sleep
$BIOSItem = "PowerManagement\DeepSleepCtrl"
$NewValue = "Disabled"
#Test if Deep Sleep exists
If (Get-Item -Path ("DellSmBios:\" + $BIOSItem) -ErrorAction SilentlyContinue) {
Write-Host ("BIOS" + [char]32 + $BIOSItem.split('\')[1] + ":" + [char]32) -NoNewline
If ((Get-Item -Path ("DellSmBios:\" + $BIOSItem)).CurrentValue -ne $NewValue) {
If ($BIOSPassword) {
Set-Item -Path ("DellSmBios:\" + $BIOSItem) -Value $NewValue -Force -Password $BIOSPassword
}
else {
Set-Item -Path ("DellSmBios:\" + $BIOSItem) -Value $NewValue -Force
}
If ((Get-Item -Path ("DellSmBios:\" + $BIOSItem)).CurrentValue -eq $NewValue) {
Write-Host $NewValue -ForegroundColor Yellow
}
else {
Write-Host (Get-Item -Path ("DellSmBios:\" + $BIOSItem)).CurrentValue -ForegroundColor Red
}
}
else {
Write-Host $NewValue -ForegroundColor Yellow
}
}
#Disable Block S3
$BIOSItem = "PowerManagement\BlockS3"
$NewValue = "Disabled"
#Test if Block S3 exists
If (Get-Item -Path ("DellSmBios:\" + $BIOSItem) -ErrorAction SilentlyContinue) {
Write-Host ("BIOS" + [char]32 + $BIOSItem.split('\')[1] + ":" + [char]32) -NoNewline
If ((Get-Item -Path ("DellSmBios:\" + $BIOSItem)).CurrentValue -ne $NewValue) {
If ($BIOSPassword) {
Set-Item -Path ("DellSmBios:\" + $BIOSItem) -Value $NewValue -Force -Password $BIOSPassword
}
else {
Set-Item -Path ("DellSmBios:\" + $BIOSItem) -Value $NewValue -Force
}
If ((Get-Item -Path ("DellSmBios:\" + $BIOSItem)).CurrentValue -eq $NewValue) {
Write-Host $NewValue -ForegroundColor Yellow
}
else {
Write-Host (Get-Item -Path ("DellSmBios:\" + $BIOSItem)).CurrentValue -ForegroundColor Red
}
}
else {
Write-Host $NewValue -ForegroundColor Yellow
}
}
#Disable C States
$BIOSItem = "PowerManagement\CStatesCtrl"
$NewValue = "Disabled"
#Test if CStatesCtrl exists
If (Get-Item -Path ("DellSmBios:\" + $BIOSItem) -ErrorAction SilentlyContinue) {
Write-Host ("BIOS" + [char]32 + $BIOSItem.split('\')[1] + ":" + [char]32) -NoNewline
If ((Get-Item -Path ("DellSmBios:\" + $BIOSItem)).CurrentValue -ne $NewValue) {
If ($BIOSPassword) {
Set-Item -Path ("DellSmBios:\" + $BIOSItem) -Value $NewValue -Force -Password $BIOSPassword
}
else {
Set-Item -Path ("DellSmBios:\" + $BIOSItem) -Value $NewValue -Force
}
If ((Get-Item -Path ("DellSmBios:\" + $BIOSItem)).CurrentValue -eq $NewValue) {
Write-Host $NewValue -ForegroundColor Yellow
}
else {
Write-Host (Get-Item -Path ("DellSmBios:\" + $BIOSItem)).CurrentValue -ForegroundColor Red
}
}
else {
Write-Host $NewValue -ForegroundColor Yellow
}
}
}
Function Set-AdvancedNIC {
#Get the Ethernet NIC
$NIC = Get-NetAdapter | Where-Object {($_.PhysicalMediaType -eq '802.3') -and ($_.Status -eq 'Up')}
#Disable Energy Efficient Ethernet setting so NIC does not go to sleep
#Two variants of Energy Efficient Exist on different Dell models
#Check if Energy-Efficient Ethernet Exists
If (Get-NetAdapterAdvancedProperty -Name $NIC.Name -DisplayName 'Energy-Efficient Ethernet' -ErrorAction SilentlyContinue) {
Write-Host 'NIC Energy-Efficient Ethernet: ' -NoNewline
Set-NetAdapterAdvancedProperty -Name $NIC.Name -DisplayName 'Energy-Efficient Ethernet' -DisplayValue 'Disabled'
If ((Get-NetAdapterAdvancedProperty -Name $NIC.Name -DisplayName 'Energy-Efficient Ethernet').DisplayValue -eq 'Disabled') {
Write-Host 'Disabled' -ForegroundColor Yellow
}
else {
Write-Host 'Enabled' -ForegroundColor Red
}
}
#Check if Energy Efficient Ethernet Exists
If (Get-NetAdapterAdvancedProperty -Name $NIC.Name -DisplayName 'Energy Efficient Ethernet' -ErrorAction SilentlyContinue) {
Write-Host 'NIC Energy Efficient Ethernet: ' -NoNewline
Set-NetAdapterAdvancedProperty -Name $NIC.Name -DisplayName 'Energy Efficient Ethernet' -DisplayValue 'Off'
If ((Get-NetAdapterAdvancedProperty -Name $NIC.Name -DisplayName 'Energy Efficient Ethernet').DisplayValue -eq 'Off') {
Write-Host 'Off' -ForegroundColor Yellow
}
else {
Write-Host 'On' -ForegroundColor Red
}
}
#Turn on Wake on Magic Packet
#Check if Wake on Magic Packet Exists
If (Get-NetAdapterAdvancedProperty -Name $NIC.Name -DisplayName 'Wake on Magic Packet' -ErrorAction SilentlyContinue) {
Write-Host 'NIC Wake on Magic Packet: ' -NoNewline
Set-NetAdapterAdvancedProperty -Name $NIC.Name -DisplayName 'Wake on Magic Packet' -RegistryKeyword '*WakeOnMagicPacket' -RegistryValue 1
If ((Get-NetAdapterAdvancedProperty -Name $NIC.Name -DisplayName 'Wake on Magic Packet').DisplayValue -eq 'Enabled') {
Write-Host 'Enabled' -ForegroundColor Yellow
}
else {
Write-Host 'Disabled' -ForegroundColor Red
}
}
#Shutdown WakeOnLAN
If (Get-NetAdapterAdvancedProperty -Name $NIC.Name -DisplayName 'Shutdown Wake-On-Lan' -ErrorAction SilentlyContinue) {
Write-Host 'NIC Shutdown Wake-On-Lan: ' -NoNewline
Set-NetAdapterAdvancedProperty -Name $NIC.Name -DisplayName 'Shutdown Wake-On-Lan' -DisplayValue 'Enabled'
If ((Get-NetAdapterAdvancedProperty -Name $NIC.Name -DisplayName 'Shutdown Wake-On-Lan').DisplayValue -eq 'Enabled') {
Write-Host 'Enabled' -ForegroundColor Yellow
}
else {
Write-Host 'Disabled' -ForegroundColor Red
}
}
}
function Set-PowerManagement {
<#
.SYNOPSIS
Enable Power Management
.DESCRIPTION
A detailed description of the Set-PowerManagement function.
.EXAMPLE
PS C:\> Set-PowerManagement
.NOTES
Additional information about the function.
#>
[CmdletBinding()]
param ()
#Turn off Hibernation
Write-Host ("OS Hiberboot:" + [char]32) -NoNewline
If ((Get-ItemProperty -Path REGISTRY::"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Power").HiberbootEnabled -ne 0) {
Set-ItemProperty -Path REGISTRY::"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Power" -Name HiberbootEnabled -Value 0 -Force
}
If ((Get-ItemProperty -Path REGISTRY::"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Power").HiberbootEnabled -eq 0) {
Write-Host "Disabled" -ForegroundColor Yellow
} else {
Write-Host "Enabled" -ForegroundColor Red
}
#0 = Option 1 & 2 checked
#10 = Option 1 checked, 2 & 3 cleared
#24 = Option 1 unchecked
#256 = Option 1, 2, & 3 all checked
#264 = Option 2 & 3 Checked
#272 = Option 1 checked
#280 = Option 2 & 3 checked
$PNPValue = 256
$Adapter = Get-NetAdapter | Where-Object { ($_.Status -eq 'Up') -and ($_.PhysicalMediaType -eq '802.3') }
$KeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002bE10318}\'
foreach ($Entry in (Get-ChildItem $KeyPath -ErrorAction SilentlyContinue).Name) {
If ((Get-ItemProperty REGISTRY::$Entry).DriverDesc -eq $Adapter.InterfaceDescription) {
$Value = (Get-ItemProperty REGISTRY::$Entry).PnPCapabilities
If ($Value -ne $PnPValue) {
Set-ItemProperty -Path REGISTRY::$Entry -Name PnPCapabilities -Value $PnPValue -Force
Disable-PnpDevice -InstanceId $Adapter.PnPDeviceID -Confirm:$false
Enable-PnpDevice -InstanceId $Adapter.PnPDeviceID -Confirm:$false
$Value = (Get-ItemProperty REGISTRY::$Entry).PnPCapabilities }
If ($Value -eq $PnPValue) {
Write-Host 'Allow the computer to turn off this device is configured' -ForegroundColor Yellow
} else {
Write-Host 'Allow the computer to turn off this device Failed' -ForegroundColor Red
Exit 1
}
}
}
}
#Set Console Title
$host.ui.RawUI.WindowTitle = $ConsoleTitle
Set-BIOS
Set-AdvancedNIC
Set-PowerManagement
</code></pre>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-6408347725183134682022-07-28T13:50:00.001-05:002022-07-28T13:50:38.038-05:00MECM System Cleanup<p>Recently, we started a cleanup of AD. Once the cleanup was completed, I wanted ConfigMgr cleaned up right away too. It is set to clean up old items, but it was not quick enough for me so I wrote the following tool that will query the All Systems collection via SQL and then reads the attributes in AD to see if the system is disabled. It will delete each disabled system from ConfigMgr at the end. Below is an example of it displaying a list of machines, to clean up, the count, and where it is deleting them. </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh41ymtXRdPehGXO7oTlZvAOWd1G5ZtGhpoxH6-Ulc-qsoc97yzcPLDb16br9i0hOCJUALKJhiZnRVisj5mNX9YGvQew9RjyUXwIO0vR5W1SjF8nkqhhamcmkb8GjKH01RYllhRtrb9EGqTb5GoEZ7fWhanmUzlB1XA65YcShwd65lWOTMgpK4mW-ac/s640/Capture.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="189" data-original-width="640" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh41ymtXRdPehGXO7oTlZvAOWd1G5ZtGhpoxH6-Ulc-qsoc97yzcPLDb16br9i0hOCJUALKJhiZnRVisj5mNX9YGvQew9RjyUXwIO0vR5W1SjF8nkqhhamcmkb8GjKH01RYllhRtrb9EGqTb5GoEZ7fWhanmUzlB1XA65YcShwd65lWOTMgpK4mW-ac/w640-h190/Capture.JPG" width="640" /></a></div><br /><p><br /></p><p>Before executing this script in your environment, I highly recommend commenting out the Remove-CMDevice cmdlet and verifying it is deleting the correct systems from ConfigMgr. </p><p>You can download the script from my <a href="https://github.com/MicksITBlogs/PowerShell/blob/fd20ac877153322e65465d7ca082e6b8ea352b57/MECMADCleanup.ps1" target="_blank">GitHub site</a>. </p><p><br /></p>
<pre style="background-attachment: initial; background-clip: initial; background-color: #f0f0f0; background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz1xGp9q86dTZmBT6Kgf4lTiCRnpB-883UDue4w0nS25otuiDMzqANNGCcUIMXUyHVEm0-djravLr2rh4_OiSiUlcv2KthEqRgIz1dikIsp_zoYcmSlQyrulsFHeMkbNa6Gd9iCnqBnO-K/s320/codebg.gif); background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> <#
.SYNOPSIS
ConfigMgr Cleanup
.DESCRIPTION
This script will compare the All Systems list in ConfigMgr to systems in AD and delete systems from ConfigMgr that are disabled in AD. It will also report a list of systems that are greater than 30 days old since the last activity in AD.
.PARAMETER SQLServer
ConfigMgr SQL Server
.PARAMETER SQLDatabase
Name of the ConfigMgr SQL Database
.PARAMETER PSHCfgMgrModule
Path to ConfigurationManager.psd1 module
.PARAMETER Sitecode
Three character ConfigMgr site code
.PARAMETER SiteServer
FQDN of the Configuration Manager server
.PARAMETER DeleteSystems
Select to automatically delete systems from Configuration Manager
.NOTES
===========================================================================
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2022 v5.8.208
Created on: 7/26/2022 8:00 AM
Created by: Mick Pletcher
Filename: MECMADCleanup.ps1
===========================================================================
#>
[CmdletBinding()]
param
(
[ValidateNotNullOrEmpty()]
[string]$SQLServer,
[ValidateNotNullOrEmpty()]
[string]$SQLDatabase,
[string]$PSHCfgMgrModule,
[string]$SiteCode,
[string]$SiteServer,
[switch]$DeleteSystems
)
function Get-PSHModule {
<#
.SYNOPSIS
Import Module
.DESCRIPTION
Import specified module
.PARAMETER Module
Name of PowerShell Module
.PARAMETER NoInstall
Import only. Typically used for modules that are not in the PowerShell Gallery
.NOTES
Additional information about the function.
#>
[CmdletBinding()]
param
(
[ValidateNotNullOrEmpty()]
[string]$Module,
[switch]$NoInstall
)
If ($NoInstall.IsPresent) {
Import-Module -Name $Module
}
else {
Try {
Import-Module -Name $Module
}
Catch {
Find-Module -Name $Module | Install-Module -Force
Import-Module -Name $Module
}
}
}
#Import SQL Server PowerShell Module
Get-PSHModule -Module "SqlServer"
#Import AD PowerShell module
Get-PSHModule -Module "ActiveDirectory"
$Systems = @()
#Get All Systems list from ConfigMgr
$List = Invoke-Sqlcmd -ServerInstance $SQLServer -Database $SQLDatabase -Query "SELECT NAME FROM dbo._RES_COLL_SMS00001 ORDER BY Name"
foreach ($System in $List) {
#Filter out built-in accounts
If (($System.Name -notlike '*Unknown*') -and ($System.Name -notlike '*Provisioning*')) {
#Return a list of all systems either not in AD or that have been disabled
Try {
$AD = Get-ADComputer $System.Name
If ($AD.Enabled -eq $false) {
$Systems += $AD.Name
}
} catch {
$Systems += $System.Name
}
}
}
#Display the systems to be deleted from ConfigMgr
$Systems
$Systems.Count
If ($Systems.Count -ne 0) {
If ($DeleteSystems.IsPresent) {
#Import ConfigMgr Module
Get-PSHModule -Module $PSHCfgMgrModule -NoInstall
If ((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $SiteServer
}
Set-Location "$($SiteCode):\"
$Systems | ForEach-Object {
Write-Host ('Deleting ' + $_ + '.....') -NoNewline
Remove-CMDevice -Name $_ -Force
If ((Get-CMDevice -Name $_) -eq $null) {
Write-Host 'Success' -ForegroundColor Yellow
} else {
Write-Host 'Failed' -ForegroundColor Red
}
}
}
}
</code></pre>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-64090461415949651172022-07-27T08:10:00.000-05:002022-07-27T08:10:02.640-05:00Configuration Manager PowerShell Module: An update to the existing console is available <p>I was recently writing a new PowerShell tool to clean up Configuration Manager of old systems. When I ran the import-module cmdlet, I got the message </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaCJhkW6lyLfGIjbuX5vpkBCWPUVKLQ0XmZG8k98zQDHFYNuudvArQNdOMppygxFhtdMHbXLPIRtH-kr0j3t_BBtotXms4ZN3-B3uxaOGNyqzUk-63TVeTfMYtHXsPVf4OcDw-j33gBmfFy8hA5BJ9FbgUvYn9OAwXUO8dPl6HtyFgaLN4qMo0F_X_/s606/Capture.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="19" data-original-width="606" height="20" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaCJhkW6lyLfGIjbuX5vpkBCWPUVKLQ0XmZG8k98zQDHFYNuudvArQNdOMppygxFhtdMHbXLPIRtH-kr0j3t_BBtotXms4ZN3-B3uxaOGNyqzUk-63TVeTfMYtHXsPVf4OcDw-j33gBmfFy8hA5BJ9FbgUvYn9OAwXUO8dPl6HtyFgaLN4qMo0F_X_/w640-h20/Capture.JPG" width="640" /></a></div><br /><p>The module was being imported from the Configuration Manager server. I always keep ConfigMgr up-to-date with the latest version within days of release and I saw the console was 5.2203.1063.2400. Doing a little digging, I found this was coming from the console that was installed on my laptop and not from the server. I also found that even if you import the PowerShell module from the server where it is the latest version, it will revert back to the machine you are working on to import from if the console is installed there. I upgraded the console on my laptop and this message disappeared. </p>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-28200870539904223532022-07-01T13:57:00.006-05:002022-07-01T14:02:00.527-05:00Bitlocker Recovery Password AD Backup and CleanupThis PowerShell script not only backs up the Bitlocker recovery password to AD but also cleans out previous backups made that do not match the current password associated with the machine. <div><br /></div><div>The script retrieves the local bitlocker password along with all recovery passwords written to AD. It will then parse through the associated passwords and remove all that does not match the one associated with the local machine. This greatly helps keep AD clean of old passwords. This script can be set up to automatically execute on a machine through Azure Automation, Orchestrator, Intune, or ConfigMgr. It will need to be executed using an account with credentials to both read the bitlocker recovery password on the local machine and modify the AD Objects. You can download the script from my <a href="https://github.com/MicksITBlogs/PowerShell/blob/master/BitlockerRecoveryPasswordADBackupCleanup.ps1" target="_blank">GitHub repository</a>. </div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2Ef2oSGZHoG-CcFgAt7fCe1Os2a425arvU93VXsDqoZZfUWvsUZpCU1F9_Zb3Sjw3qYw9JfshyclEn7xcDJijSEAfDAigsLdVvawEyGPhWBVH0zxojb58L30vJ6O5Cou_uNZLHmvpeFbvp-NaBrgf0HSef4ugUpz723kRCXP4dWRKuoyovLUYyau3/s583/Capture.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="75" data-original-width="583" height="51" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2Ef2oSGZHoG-CcFgAt7fCe1Os2a425arvU93VXsDqoZZfUWvsUZpCU1F9_Zb3Sjw3qYw9JfshyclEn7xcDJijSEAfDAigsLdVvawEyGPhWBVH0zxojb58L30vJ6O5Cou_uNZLHmvpeFbvp-NaBrgf0HSef4ugUpz723kRCXP4dWRKuoyovLUYyau3/w400-h51/Capture.JPG" width="400" /></a></div><br /><div><br /></div><div><br /></div><div>NOTE: If you do not want the script to display the recovery password, you can comment out the write-host lines.</div><div><br /></div><div><br /></div><div><br /></div>
<pre style="background-attachment: initial; background-clip: initial; background-color: #f0f0f0; background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz1xGp9q86dTZmBT6Kgf4lTiCRnpB-883UDue4w0nS25otuiDMzqANNGCcUIMXUyHVEm0-djravLr2rh4_OiSiUlcv2KthEqRgIz1dikIsp_zoYcmSlQyrulsFHeMkbNa6Gd9iCnqBnO-K/s320/codebg.gif); background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> <#
.NOTES
===========================================================================
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2022 v5.8.208
Created on: 7/1/2022 11:08 AM
Created by: Mick Pletcher
Filename: BitlockerRecoveryPasswordADBackupCleanup.ps1
===========================================================================
.DESCRIPTION
This script will delete active directory entries that contain the Bitlocker recovery keys which do not match to the current one. It will then push up the new key to AD.
#>
[CmdletBinding()]
param ()
Clear-Host
#Get the local bitlocker password
$LocalPassword = ((manage-bde -protectors -get ($env:ProgramFiles).split('\')[0] -id ((Get-WmiObject -Namespace 'Root\cimv2\Security\MicrosoftVolumeEncryption' -Class 'Win32_EncryptableVolume').GetKeyProtectors(3).volumeKeyprotectorID)).trim() | Where-Object { $_.Trim() -ne '' })[-1]
$BitlockerID = (((manage-bde -protectors -get ($env:ProgramFiles).split('\')[0] -id ((Get-WmiObject -Namespace 'Root\cimv2\Security\MicrosoftVolumeEncryption' -Class 'Win32_EncryptableVolume').GetKeyProtectors(3).volumeKeyprotectorID)).trim() | Where-Object { $_.Trim() -ne '' })[-3]).split(":")[1].trim()
#Get all bitlocker entries from active directory
$ADEntries = (Get-ADObject -Filter { objectclass -eq 'msFVE-RecoveryInformation' } -SearchBase (Get-ADComputer $env:COMPUTERNAME).DistinguishedName -Properties 'msFVE-RecoveryPassword')
#Number of recovery key entries stored in AD
$EntryCount = 0
#Parse through all active directory entries removing ones that do not contain local bitlocker password
foreach ($Item in $ADEntries) {
If ($LocalPassword -ne $Item.'msFVE-RecoveryPassword') {
Remove-ADObject -Identity $Item.DistinguishedName -Confirm:$false
}
else {
$EntryCount += 1
If ($EntryCount -gt 1) {
Remove-ADObject -Identity $Item.DistinguishedName -Confirm:$false
}
}
}
$ADEntries = (Get-ADObject -Filter { objectclass -eq 'msFVE-RecoveryInformation' } -SearchBase (Get-ADComputer $env:COMPUTERNAME).DistinguishedName -Properties 'msFVE-RecoveryPassword')
#Backup the bitlocker password to active directory if it is not in any AD entries
If ($LocalPassword -notin $ADEntries.'msFVE-RecoveryPassword') {
#Backup recovery key to active directory
$Switches = "-protectors -adbackup c: -id" + [char]32 + $BitlockerID
Write-Host "Backing up to AD....." -NoNewline
$ErrCode = (Start-Process -FilePath $env:windir'\system32\manage-bde.exe' -ArgumentList $Switches -PassThru -Wait).ExitCode
If ($ErrCode -eq 0) {
Write-Host "Success" -ForegroundColor Yellow
$ADEntries = (Get-ADObject -Filter { objectclass -eq 'msFVE-RecoveryInformation' } -SearchBase (Get-ADComputer $env:COMPUTERNAME).DistinguishedName -Properties 'msFVE-RecoveryPassword')
Write-Host
Write-Host " Bitlocker ID:" -NoNewline
Write-Host $BitlockerID -ForegroundColor Yellow
Write-Host "Local Password:" -NoNewline
Write-Host $LocalPassword -ForegroundColor Yellow
Write-Host " AD Password:" -NoNewline
Write-Host $ADEntries.'msFVE-RecoveryPassword' -ForegroundColor Yellow
If ($LocalPassword -eq $ADEntries.'msFVE-RecoveryPassword') {
Exit 0
}
}
elseif ($ErrCode -eq "-2147024809") {
$Status = [string]((manage-bde.exe -status).replace(' ', '')).split(":")[16]
If ($Status -eq "FullyDecrypted") {
Write-Host "Failed. System is not Bitlockered"
Exit 2
}
else {
Write-Host "Unspecified error"
Exit 3
}
}
else {
Write-Host "Failed with error code"$ErrCode -ForegroundColor Red
Write-Host
Write-Host " Bitlocker ID:" -NoNewline
Write-Host $BitlockerID -ForegroundColor Yellow
Write-Host "Local Password:" -NoNewline
Write-Host $LocalPassword -ForegroundColor Yellow
Write-Host " AD Password:" -NoNewline
Write-Host $ADEntries.'msFVE-RecoveryPassword' -ForegroundColor Yellow
Exit 1
}
}
else {
Write-Host
Write-Host " Bitlocker ID:"$BitlockerID
Write-Host "Local Password:"$LocalPassword
Write-Host " AD Password:"$ADEntries.'msFVE-RecoveryPassword'
Exit 0
}
</code></pre>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-19207909840605892312022-06-30T15:22:00.002-05:002022-06-30T15:22:51.239-05:00Automating Dell TPM Configuration<p>Over the years, we have manually configured the TPM before imaging a system as part of our build process. Dell has since given the ability to automate the entire process after giving the option to automate clearing the TPM. That was always been the big stopper in full automation. </p><p>I wrote a series of scripts that I put into the build process that do all of the necessary steps in readying the TPM for bitlocker as shown below. The Smart Reporting, and Wake-On-LAN are additional features I added that do not pertain to the TPM and Bitlocker. The Conditional Reboot is another PowerShell script I wrote that checks if the system is waiting for a reboot and reboots the system if necessary. If interested, this is in another blog of mine.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiq_QuqnqCDzNBJg6QonGij-cMBSXgMBKM6mCR6XcTiaEuMcTszgMLGHe_9kKaD5LpI452lIknwSfhjAk9mq3Rg2_vPZ3KR84P4wiEDTO3lBzx9s2tVwpV1xzPi8ot1l932RuKEHKoe2FpUox_kNwHq_sI5ahiIgzs5bPRoVHtS_-bt5FMneY2Xv731/s309/Capture.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="309" data-original-width="203" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiq_QuqnqCDzNBJg6QonGij-cMBSXgMBKM6mCR6XcTiaEuMcTszgMLGHe_9kKaD5LpI452lIknwSfhjAk9mq3Rg2_vPZ3KR84P4wiEDTO3lBzx9s2tVwpV1xzPi8ot1l932RuKEHKoe2FpUox_kNwHq_sI5ahiIgzs5bPRoVHtS_-bt5FMneY2Xv731/w263-h400/Capture.JPG" width="263" /></a></div><br /><p>Here are the files to download. The names correspond with the list shown above so you know what sequence to put them in. I clear the BIOS password at the start and then reset it near the end as setting the PPI TPM Clear requires a BIOS password to be in place to check off that box via a script. These scripts were successfully tested on Dell Optiplex 7070 and Latitude 7420. There was nothing manual that had to be done to ready the TPM for bitocker. </p><p></p><ul style="text-align: left;"><li><a href="https://github.com/MicksITBlogs/PowerShell/blob/master/ClearDellBIOSPassword.ps1" target="_blank">ClearDellBIOSPassword.ps1</a></li><li><a href="https://github.com/MicksITBlogs/PowerShell/blob/master/ClearTPM.ps1" target="_blank">ClearTPM.ps1</a></li><li><a href="https://github.com/MicksITBlogs/PowerShell/blob/master/InitializeTPM.ps1" target="_blank">InitializeTPM.ps1</a></li><li><a href="https://github.com/MicksITBlogs/PowerShell/blob/master/InitializeTPM.ps1" target="_blank">PPI.ps1</a></li><li><a href="https://github.com/MicksITBlogs/PowerShell/blob/master/SetDellBIOSPassword.ps1" target="_blank">SetDellBIOSPassword.ps1</a></li><li><a href="https://github.com/MicksITBlogs/PowerShell/blob/master/TurnOnTPM.ps1" target="_blank">TurnOnTPM.ps1</a></li><li><a href="https://github.com/MicksITBlogs/PowerShell/blob/master/WakeOnLAN.ps1" target="_blank">WakeOnLAN.ps1</a></li></ul><p></p>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-55067172804495112042022-03-17T09:15:00.001-05:002022-03-17T09:15:23.504-05:00Identify Machines a User is Logged Into using Carbon BlackIf you have Carbon Black in your environment, you can use it to identify which machines a user account is logged into. Carbon Black collects a vast amount of data on machines and reports it to the cloud database. The following is how to use Carbon Black to list the machines:<div><ol style="text-align: left;"><li>Log into the Carbon Black Cloud Portal</li><li>Click the Investigate tab</li><li>In the investigate search field at the top, enter the following: </li><ol><li>Enter <b>process_username:<username></b> in the search field at the top. <username> needs to be changed to the actual username you are searching for.</li><li>Change the time field to the right to within one day or less</li><li>Click the magnifying glass on the far right to search. </li></ol><li>Under the filters field, scroll down to Device and it will show a list of devices the profile is currently logged into. </li></ol><div>As you can see in the screenshot under devices, it returned two machines my profile was logged into. </div></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjK_LzTUVvzpAUWzSsohsTCa5HRorD70AbK1YPio-Ry4BQwap6E73_Tlg9nHoQT0B7FVx5zx-A4A9e5SPFzKb1RFtEczqp_7kOG8YJDYYezPYeTCt7KSAPkKOed7hQ1WiXG9vvXQ9BeWpQ7v5gGoGzf8vaoBLpbs8wGG_jDeZSiyOUA-Bo4xPkaF0c1=s1441" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="789" data-original-width="1441" height="350" src="https://blogger.googleusercontent.com/img/a/AVvXsEjK_LzTUVvzpAUWzSsohsTCa5HRorD70AbK1YPio-Ry4BQwap6E73_Tlg9nHoQT0B7FVx5zx-A4A9e5SPFzKb1RFtEczqp_7kOG8YJDYYezPYeTCt7KSAPkKOed7hQ1WiXG9vvXQ9BeWpQ7v5gGoGzf8vaoBLpbs8wGG_jDeZSiyOUA-Bo4xPkaF0c1=w640-h350" width="640" /></a></div><br /><div><br /></div>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-14429858409431778632022-03-16T08:22:00.002-05:002022-03-16T08:22:50.495-05:00Last Server Reboot Reporting<p>Recently, we needed a report of the last boot time of all servers. I wrote this PowerShell script that queries AD for a list of all windows servers and then does a WMI query on each server for the LastBootUpTime. It then calculates the number of days and writes this to an object with the computer name and the number of days since the last reboot. It will write this info to a CSV file. </p><p>You can download the script from my <a href="https://github.com/MicksITBlogs/PowerShell/blob/master/LastReboot.ps1" target="_blank">GitHub site</a>.</p><pre style="background-attachment: initial; background-clip: initial; background-color: #f0f0f0; background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz1xGp9q86dTZmBT6Kgf4lTiCRnpB-883UDue4w0nS25otuiDMzqANNGCcUIMXUyHVEm0-djravLr2rh4_OiSiUlcv2KthEqRgIz1dikIsp_zoYcmSlQyrulsFHeMkbNa6Gd9iCnqBnO-K/s320/codebg.gif); background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> Import-Module -Name ActiveDirectory -Force
#Get list of windows based servers
$Servers = Get-ADComputer -Filter * -Properties * | Where-Object {$_.OperatingSystem -like '*windows server*'} | Select Name | Sort-Object -Property Name
#Create Report Array
$Report = @()
#Parse through server list
Foreach ($Server in $Servers) {
#Get the computer name
$ComputerName = ([String]$Server).Split("=")[1].Split("}")[0].Trim()
#Check if the system is online
If ((Test-Connection -ComputerName $ComputerName -Count 1 -Quiet) -eq $true) {
#Query last bootup time and use $Null if unobtainable
Try {
$LastBootTime = (Get-CimInstance -ClassName win32_operatingsystem -ComputerName $ComputerName -ErrorAction SilentlyContinue).LastBootUpTime
$LastBoot = (New-TimeSpan -Start $LastBootTime -End (Get-Date)).Days
} Catch {
$LastBoot = $null
}
#Add computername and last boot time to the object
If ($ComputerName -ne $null) {
$SystemObject = New-Object -TypeName psobject
$SystemObject | Add-Member -MemberType NoteProperty -Name ComputerName -Value $ComputerName
$SystemObject | Add-Member -MemberType NoteProperty -Name DaysSinceLastBoot -Value $LastBoot
$Report += $SystemObject
}
} else {
$SystemObject = New-Object -TypeName psobject
$SystemObject | Add-Member -MemberType NoteProperty -Name ComputerName -Value $ComputerName
$SystemObject | Add-Member -MemberType NoteProperty -Name DaysSinceLastBoot -Value 'OFFLINE'
$Report += $SystemObject
}
$ComputerName = $null
}
#Print report to screen
$Report
$OutFile = "C:\Users\Desktop\LastRebootReport.csv"
#Delete CSV file if it already exists
If ((Test-Path -Path $OutFile) -eq $true) {
Remove-Item -Path $OutFile -Force
}
#Export report to CSV file
$Report | Export-Csv -Path $OutFile -NoClobber -Encoding UTF8 -NoTypeInformation -Force
</code></pre>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-46671125847261181432022-03-07T12:28:00.002-06:002022-03-07T12:28:36.796-06:00Configure SQL Server Firewall Ports with PowerShell<p> I recently had to rebuild the Configuration Manager server. As I was running the prerequisite tool, it showed it could not communicate with the SQL server that is separate from the Configuration Manager Server. The issue ended up being ports needed to be opened up. </p><p>This PowerShell script will configure the correct ports. It also adds to the description as to what services the port is opened up for in Configuration Manager. If the rule is already present, it skips over. If you open up a rule after the script is executed, you will see it says This is a predefined rule and some of its properties cannot be modified. This was caused by me adding the rule to the group Configuration Manager. If -Group is removed from the cmdlet, this message disappears. </p><p>You can download the script from <a href="https://github.com/MicksITBlogs/PowerShell/blob/master/ConfigMgrSQLFirewallSettings.ps1" target="_blank">here</a>.</p><p><br /></p>
<pre style="background-attachment: initial; background-clip: initial; background-color: #f0f0f0; background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz1xGp9q86dTZmBT6Kgf4lTiCRnpB-883UDue4w0nS25otuiDMzqANNGCcUIMXUyHVEm0-djravLr2rh4_OiSiUlcv2KthEqRgIz1dikIsp_zoYcmSlQyrulsFHeMkbNa6Gd9iCnqBnO-K/s320/codebg.gif); background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> If ((Get-NetFirewallRule -Name "ConfigMgr Port 135 UDP" -ErrorAction SilentlyContinue) -eq $null) {
New-NetFirewallRule -Name "ConfigMgr Port 135 UDP" -DisplayName "ConfigMgr Port 135 UDP" -Description "Site Server" -Group "Configuration Manager" -Profile "Domain" -Protocol UDP -LocalPort 135 -Enabled True
}
If ((Get-NetFirewallRule -Name "ConfigMgr Port 135 TCP" -ErrorAction SilentlyContinue) -eq $null) {
New-NetFirewallRule -Name "ConfigMgr Port 135 TCP" -DisplayName "ConfigMgr Port 135 TCP" -Description "Site Server" -Group "Configuration Manager" -Profile "Domain" -Protocol TCP -LocalPort 135 -Enabled True
}
If ((Get-NetFirewallRule -Name "ConfigMgr Port 1433 TCP" -ErrorAction SilentlyContinue) -eq $null) {
New-NetFirewallRule -Name "ConfigMgr Port 1433 TCP" -DisplayName "ConfigMgr Port 1433 TCP" -Description "Asset Intelligence Synchronization Point, App Catalog Web Service Point, Endpoint Protection, Enrollment Point, MP, Reporting point, Site Server, SMS Provider, SQL Server, SMP" -Group "Configuration Manager" -Profile "Domain" -Protocol TCP -LocalPort 1433 -Enabled True
}
If ((Get-NetFirewallRule -Name "ConfigMgr Port 4022 TCP" -ErrorAction SilentlyContinue) -eq $null) {
New-NetFirewallRule -Name "ConfigMgr Port 4022 TCP" -DisplayName "ConfigMgr Port 4022 TCP" -Description "SQL Server" -Group "Configuration Manager" -Profile "Domain" -Protocol TCP -LocalPort 4022 -Enabled True
}
If ((Get-NetFirewallRule -Name "ConfigMgr Port 445 TCP" -ErrorAction SilentlyContinue) -eq $null) {
New-NetFirewallRule -Name "ConfigMgr Port 445 TCP" -DisplayName "ConfigMgr Port 445 TCP" -Description "Site Server" -Group "Configuration Manager" -Profile "Domain" -Protocol TCP -LocalPort 445 -Enabled True
}
</code></pre>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-56005401553482196812022-01-24T15:23:00.002-06:002022-01-25T08:26:32.158-06:00Troubleshooting No Task Sequences are available (Tasksequence.xml does not exist, is empty, or is inaccessible)<p> I encountered this error when working with MDT right after deleting two old task sequences. </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEg6Pq4bSBy3geekp0CquVF3Dd92cdx2EJcstQ6kXuu68dy9aTyh9b-YulLYF_WVF2xvYI6podq3tTMDFfYgVUq_XUp6hW1eHdXK6ysbUGA2TOTivSAiax_5GQ-h3a5IizojTCVMwX4Oja9j5F8SvocCjPKC9vtrD_WSxtS27Q4qFzWNz-HNT2LQK47Q=s4032" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="3024" data-original-width="4032" height="480" src="https://blogger.googleusercontent.com/img/a/AVvXsEg6Pq4bSBy3geekp0CquVF3Dd92cdx2EJcstQ6kXuu68dy9aTyh9b-YulLYF_WVF2xvYI6podq3tTMDFfYgVUq_XUp6hW1eHdXK6ysbUGA2TOTivSAiax_5GQ-h3a5IizojTCVMwX4Oja9j5F8SvocCjPKC9vtrD_WSxtS27Q4qFzWNz-HNT2LQK47Q=w640-h480" width="640" /></a></div><br /><p>To troubleshoot this, I went to the logs directory in the MDT hierarchy. I specified in the customsettings.ini file to write all build logs to this specific directory:</p><p>SLShare=\\BUILD\PRODDeploymentShare$\Logs</p><p>SLShareDynamicLogging=\\BUILD\PRODDeploymentShare$\Logs\%ComputerName%</p><p>When I went into the logs, I opened up the only log there, BDD.log, and found that it could not find the task sequence specified in the TaskSequences.xml file. I then opened up the TaskSequences.xml file, which is located at %DEPLOYROOT%\Control\TaskSequences.xml. Once I opened up the XML, I searched for the task sequence no found and deleted it. That fixed the issue. </p><div class="separator" style="clear: both; text-align: center;"><br /></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiHDSlva29kwFgq1yEnqRRgR6AaMpvrxszm0LhzWTJZ5Y612G5WriCPzG0QC_XuS2GQsG9iAsuii56AMV-bjMsJjmrJ04IfI9mlvL2ZyLcIXygi9F74XIMaMeMZX7tc4QsmlIfrgEjuvdUqA0IMsdcvt2LJbU_onpGRA5hqVMktiewlOVq39XXXKfst=s1431" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="118" data-original-width="1431" height="52" src="https://blogger.googleusercontent.com/img/a/AVvXsEiHDSlva29kwFgq1yEnqRRgR6AaMpvrxszm0LhzWTJZ5Y612G5WriCPzG0QC_XuS2GQsG9iAsuii56AMV-bjMsJjmrJ04IfI9mlvL2ZyLcIXygi9F74XIMaMeMZX7tc4QsmlIfrgEjuvdUqA0IMsdcvt2LJbU_onpGRA5hqVMktiewlOVq39XXXKfst=w640-h52" width="640" /></a></div><br /><p><br /></p>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-66425868288319128792022-01-21T08:57:00.001-06:002022-01-21T08:57:47.727-06:00How to effectively add Office updates to the update folder<p>If you are still using an on-prem version of office, you know the need to populate the Updates folder so updates get applied when Office is installed instead of having to wait for the updates to download and then be installed. The issue I have run into is some of the updates, once extracted, are named the same. To resolve this, I extract each update from the cab file. Next, I open up the msp file using ORCA. In the MsiPatchMetadata table, you will find the KB number in the Release property. I use that number and rename the msp file to KB4011634.msp for instance. This prevents files from being overwritten. I then copy it back to the Updates folder. Office is able to read the MSP file with no problems. </p><p><br /></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpfvu0K7OxXm1u4ZtdB55QQT2gP0S52bra2bcZ4H_3qpv-q4efcOgrjsFP9CRdRwN5g6igB0ZdeodZNoF8hbJf1bUzQradAk-jMsPq-bFioXv5I80ir9RMpQo34Fhs-Ak2auTtwlepglE/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="549" data-original-width="815" height="432" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpfvu0K7OxXm1u4ZtdB55QQT2gP0S52bra2bcZ4H_3qpv-q4efcOgrjsFP9CRdRwN5g6igB0ZdeodZNoF8hbJf1bUzQradAk-jMsPq-bFioXv5I80ir9RMpQo34Fhs-Ak2auTtwlepglE/w640-h432/image.png" width="640" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-7859474312616634462021-12-20T11:02:00.002-06:002021-12-20T11:02:43.448-06:00Installing WinGet via PowerShell<p> I wanted to install Winget via PowerShell in an automated process. The first thing I did was to go to the <a href="https://github.com/microsoft/winget-cli/releases" target="_blank">Winget GitHub site</a> and select the latest version of the Windows Package Manager. Under the latest version page, I clicked on the file with the extension of msixbundle and downloaded it to my machine.</p><p>Now that it is on your machine, you can automate the installation with a PowerShell one-liner script where the PS1 file resides in the same directory as the Winget installer. The script will search the current directory for a msixbundle file with the cmdlet listed below. </p><pre style="background-attachment: initial; background-clip: initial; background-color: #f0f0f0; background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz1xGp9q86dTZmBT6Kgf4lTiCRnpB-883UDue4w0nS25otuiDMzqANNGCcUIMXUyHVEm0-djravLr2rh4_OiSiUlcv2KthEqRgIz1dikIsp_zoYcmSlQyrulsFHeMkbNa6Gd9iCnqBnO-K/s320/codebg.gif); background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> Add-AppPackage -Path ((Get-ChildItem -Path ((Get-Location).ProviderPath) -Filter *.msixbundle).FullName)
</code></pre><br />Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-90793145947967970282021-11-19T14:19:00.004-06:002021-11-19T14:19:57.338-06:00Administrator Reporting Tool<p>This PowerShell tool queries AD for a list of machines in the specified administrative admin groups. The list is specified by modifying the script with the groups to be queried. The following example shows how to add groups to the query. When there are multiple groups, they can be divided off by using the pipe character between each group.</p><p>Where-Object {$_.MemberOf -match 'Admins|Domain Admins|System Admins|'}</p><p>There is the parameter called $Days. This specifies how many days old the account needs to be so it is not displayed in the report anymore. </p><p>I wrote this script so that it can easily be used with Orchestrator or as a scheduled task. It exits the script with an error code 0 if there is data to emailed along with the Write-Output statement that puts the list of users in the output of the program once exited. If there was no data to return, it exits with an error code 1 so that Orchestrator knows not to proceed with the email task. </p><p>You can download the script from my <a href="https://github.com/MicksITBlogs/PowerShell/blob/master/AdministratorReport.ps1" target="_blank">GiHub Site</a>. <br /><br /></p>
<pre style="background-attachment: initial; background-clip: initial; background-color: #f0f0f0; background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz1xGp9q86dTZmBT6Kgf4lTiCRnpB-883UDue4w0nS25otuiDMzqANNGCcUIMXUyHVEm0-djravLr2rh4_OiSiUlcv2KthEqRgIz1dikIsp_zoYcmSlQyrulsFHeMkbNa6Gd9iCnqBnO-K/s320/codebg.gif); background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> <#
.SYNOPSIS
Administrator Report
.DESCRIPTION
This tool is intended to keep staff informed of new administrator accounts. This script queries for a list of users in the specified administrator group(s). It then produces a list of the administrator users that got created within the specified number of days.
.PARAMETER Days
Number of days since the administrator account was created
.NOTES
===========================================================================
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2021 v5.8.195
Created on: 11/9/2021 1:37 PM
Created by: Mick Pletcher
Filename: AdministratorReport.ps1
===========================================================================
#>
Param
(
[ValidateNotNullOrEmpty()][int]$Days = 1
)
#Retrieves a list of users from AD and filters them by association with the specied security groups. The match can be associated with multiple groups separated with a pipe
#Example: Where-Object {$_.MemberOf -match '|Domain Admins|System Admins|'}
$Users = Get-ADUser -Filter * -Properties MemberOf | Where-Object {$_.MemberOf -match 'Super Admins|Domain Admins'}
#Filter out all accounts that are older than the specified $Days
$Users | ForEach-Object {
If ((New-TimeSpan -Start ((Get-ADUser -Identity $_.SamAccountName -Properties whenCreated).whenCreated) -End (Get-Date)).Days -le $Days) {
$NewUsers += $_.Name
}
}
If (($NewUsers -ne $null) -and ($NewUsers -ne '')) {
Write-Output $NewUsers
} Else {
Exit 1
}
Exit 0
</code></pre>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-10608691961451554592021-10-13T09:48:00.007-05:002021-10-13T14:29:29.155-05:00Installing Printers via ConfigMgr for Non-Admin Users<p>KB5005652 resolved the "PrintNightMare" vulnerability, but it also brought many companies to a halt when it came to end-users installing printers if they did not have administrator privileges. During the time since the update, we had our help desk install printers for users on an as-needed basis. There was a workaround by setting the <span face=""Segoe UI", "Segoe UI Web", wf_segoe-ui_normal, "Helvetica Neue", "BBAlpha Sans", "S60 Sans", Arial, sans-serif" style="background-color: #f4f4f4; color: #1e1e1e; font-size: 16px;">RestrictDriverInstallationToAdministrators</span> to 0 to override the fix, but this would have thwarted the security vulnerability. </p><p>I came up with the fix to install printers using configuration manager since it can install them with a privileged account. The solution in my environment was to use PowerShell to query the print servers for a list of offices and printers in our scenario. After the office is selected, it displays a list of printers in that office for the user to select from. Once that printer is selected, it will check if the printer is already installed. If so, it will delete it and reinstall. If not, it will install the new printer. Finally, it will verify that the printer was successfully installed. I currently have the script read from a text file the list of print servers with the associated city location in CSV format. I did make a last-minute change where I hardcoded the locations into the object creation instead of using it from the text file. I will probably find a better way in the near future and update this with something like using Get-ADObject to get a list of the print servers, but will still need to find where the link to the associated city name is to use that method. For now, this is working great in our environment.</p><p>This has to be set up in configuration manager as a package advertisement with <b>Allow users to interact with this program</b> checked. This allows users to install a printer on-demand through Software Center and rerun the package as many times as needed. </p><p><b>NOTE:</b> One last important thing. This script was written for our environment. You will need PowerShell knowledge to modify this script to work in your environment. This is more of a primer to show you how I overcame the limitation in this corporate environment. The script will likely need to be greatly modified for some environments. The size of the company will also make a lot of difference in how this script needs to be modified. The firm I am at is roughly 500 people. A GUI interface may be preferential to some, which I may come back later and implement with PowerShell Studio. </p><p>You can download the PowerShell script from my <a href="https://github.com/MicksITBlogs/PowerShell/blob/master/PrinterInstaller.ps1" target="_blank">Github repository</a>. </p><p><br /></p>
<pre style="background-attachment: initial; background-clip: initial; background-color: #f0f0f0; background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz1xGp9q86dTZmBT6Kgf4lTiCRnpB-883UDue4w0nS25otuiDMzqANNGCcUIMXUyHVEm0-djravLr2rh4_OiSiUlcv2KthEqRgIz1dikIsp_zoYcmSlQyrulsFHeMkbNa6Gd9iCnqBnO-K/s320/codebg.gif); background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> <#
.SYNOPSIS
PrinterInstaller
.DESCRIPTION
This script is intended to be used in ConfigMgr as an applicatio advertisement in Software Center. This allows non-admin users to install printers allowing companies to keep the Microsoft print server patch in place. It nwill retrieve all printers from all print servers. It then prompts the user for the office. At that point, it will display a list of printers in that office for the user to select from. Finally, it will check if the printer is already installed. If it is, it will uninstall the printer and proceed to install it, otherwise it will install the printer.
.PARAMETER PrintServersFile
Name of the file which contains a list of print servers
.NOTES
===========================================================================
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2021 v5.8.194
Created on: 10/12/2021 7:40 PM
Created by: Mick Pletcher
Filename: PrinterInstaller.ps1
===========================================================================
#>
[CmdletBinding()]
Param
(
[ValidateNotNullOrEmpty()]$PrintServersFile = 'PrintServers.txt'
)
Clear-Host
Write-Host "Retrieving List of Offices..."
#Create Printers array
$Printers = @()
#Get list of print servers that includes the print server and printer location
$PrintServers = Get-Content -Path ($PSScriptRoot + '\' + $PrintServersFile)
#Get list of all printers from within each print server
$PrintServers | ForEach-Object {
#Test if the print server is online before querying it
If ((Test-Connection -ComputerName $_.Split(",")[0] -Count 1 -Quiet) -eq $true) {
#Add all printer from the specified print server
$Query += Get-Printer -ComputerName $_.Split(",")[0]
}
}
#Create the object for each printer and add it to the $Printers array
$Query | ForEach-Object {
$object = New-Object PSObject
$object | Add-Member Noteproperty -Name PrinterName -Value $_.Name
$object | Add-Member Noteproperty -Name PrinterPort -Value $_.PortName
$object | Add-Member Noteproperty -Name PrintServer -Value $_.ComputerName
$object | Add-Member Noteproperty -Name DriverName -Value $_.DriverName
Switch ($_.ComputerName) {
"Printer1" { $object | Add-Member Noteproperty -Name Office -Value "Austin" }
"Printer2" { $object | Add-Member Noteproperty -Name Office -Value "Birmingham" }
"Printer3" { $object | Add-Member Noteproperty -Name Office -Value "Chattanooga" }
"Printer4" { $object | Add-Member Noteproperty -Name Office -Value "Nashville" }
}
#Add a floor value to the Floor object if the print server is in Nashville
If ($_.ComputerName -eq 'Printer4') {
$object | Add-Member Noteproperty -Name Floor -Value ($_.Name.Split("-")[1])
} else {
#Leave the floor object blank if it is any office other than Nashville
$object | Add-Member Noteproperty -Name Floor -Value ""
}
#Add the object to the $Printers array
$Printers += $object
}
#Sort the array by Office and then Floor
$Printers = $Printers | Sort-Object -Property Office, Floor
#Counter for selecting the office
$Count = 1
#Display each office with a number selection
$PrintServers | ForEach-Object {Write-Host ([string]$Count + ' - ' + $_.Split(",")[1]);$Count++}
#Prompt for a user selection of the office
$Selection = Read-Host -Prompt "Select the office"
#Get list of printers for selected office
$PrintersSelection = $Printers | Where-Object {$_.PrintServer -eq ($PrintServers[$Selection - 1].Split(",")[0])}
#printer counter
$Count = 1
Clear-Host
Write-Host
Write-Host "Retrieving list of Printers..."
#Display list of printers in the selected office
$PrintersSelection | ForEach-Object {Write-Host ([string]$Count + ' - ' + $_.PrinterName);$Count++}
#Prompt the user to select the printer
$Selection = Read-Host -Prompt "Select the Printer"
#Display the selected printer
$PrintersSelection[$Selection - 1]
#Check if the printer is installed and uninstall it if true
If ((Get-Printer -Name $PrintersSelection[$Selection - 1].PrinterName -ErrorAction SilentlyContinue) -ne $null) {
Remove-Printer -Name $PrintersSelection[$Selection - 1].PrinterName
Remove-PrinterPort -Name $PrintersSelection[$Selection - 1].PrinterPort
}
Write-Host
Write-Host ('Installing Printer' + [char]32 + $PrintersSelection[$Selection - 1].PrinterName + '.....') -NoNewline
#Install the selected printer
Add-PrinterPort -Name $PrintersSelection[$Selection - 1].PrinterPort -PrinterHostAddress $PrintersSelection[$Selection - 1].PrinterPort
Add-Printer -Name $PrintersSelection[$Selection - 1].PrinterName -DriverName $PrintersSelection[$Selection - 1].DriverName -PortName $PrintersSelection[$Selection - 1].PrinterPort
#Verify the printer was installed
If ((Get-Printer -Name $PrintersSelection[$Selection - 1].PrinterName -ErrorAction SilentlyContinue) -ne $null) {
Write-Host 'success' -ForegroundColor Yellow
} Else {
Write-Host 'failed' -ForegroundColor Red
}
</code></pre>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-62522645044395860772021-06-29T14:40:00.000-05:002021-06-29T14:40:28.111-05:00PowerShell: Install FontsFont installation using PowerShell has changed since Windows 10 1909. The old way of doing it with PowerShell no longer works. This new script was <a href="https://powers-hell.com/2020/06/09/installing-fonts-with-powershell-intune/" target="_blank">originally</a> written by <a href="https://powers-hell.com/2020/06/09/installing-fonts-with-powershell-intune/" target="_blank">Ben Reader</a> who is a fellow Microsoft MVP from Australia. I took the script and modified it by improving on user interface and file tweaks. <div><br /></div><div>This script gets a list of all font files that exist in the same directory. It then parses through each font file getting the font name attribute at which point it copies the file to c:\windows\Fonts. Next, it registers the font by writing the font file name to HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Fonts so that it will show up in a drop-down menu. The script also writes the status of each step to the screen so the person executing it knows if each font install is successful or not. </div><div><br /></div><div>You can download the script from my <a href="https://github.com/MicksITBlogs/PowerShell/blob/master/InstallFonts.ps1" target="_blank">GitHub site</a>. </div><div><br /></div><div><br /></div>
<pre style="background-attachment: initial; background-clip: initial; background-color: #f0f0f0; background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz1xGp9q86dTZmBT6Kgf4lTiCRnpB-883UDue4w0nS25otuiDMzqANNGCcUIMXUyHVEm0-djravLr2rh4_OiSiUlcv2KthEqRgIz1dikIsp_zoYcmSlQyrulsFHeMkbNa6Gd9iCnqBnO-K/s320/codebg.gif); background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> <#
.SYNOPSIS
Install Open Text and True Type Fonts
.DESCRIPTION
This script will install OTF and TTF fonts that exist in the same directory as the script.
.NOTES
===========================================================================
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2021 v5.8.187
Created on: 6/24/2021 9:36 AM
Created by: Mick Pletcher
Filename: InstallFonts.ps1
===========================================================================
#>
<#
.SYNOPSIS
Install the font
.DESCRIPTION
This function will attempt to install the font by copying it to the c:\windows\fonts directory and then registering it in the registry. This also outputs the status of each step for easy tracking.
.PARAMETER FontFile
Name of the Font File to install
.EXAMPLE
PS C:\> Install-Font -FontFile $value1
.NOTES
Additional information about the function.
#>
function Install-Font {
param
(
[Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][System.IO.FileInfo]$FontFile
)
#Get Font Name from the File's Extended Attributes
$oShell = new-object -com shell.application
$Folder = $oShell.namespace($FontFile.DirectoryName)
$Item = $Folder.Items().Item($FontFile.Name)
$FontName = $Folder.GetDetailsOf($Item, 21)
try {
switch ($FontFile.Extension) {
".ttf" {$FontName = $FontName + [char]32 + '(TrueType)'}
".otf" {$FontName = $FontName + [char]32 + '(OpenType)'}
}
$Copy = $true
Write-Host ('Copying' + [char]32 + $FontFile.Name + '.....') -NoNewline
Copy-Item -Path $fontFile.FullName -Destination ("C:\Windows\Fonts\" + $FontFile.Name) -Force
#Test if font is copied over
If ((Test-Path ("C:\Windows\Fonts\" + $FontFile.Name)) -eq $true) {
Write-Host ('Success') -Foreground Yellow
} else {
Write-Host ('Failed') -ForegroundColor Red
}
$Copy = $false
#Test if font registry entry exists
If ((Get-ItemProperty -Name $FontName -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Fonts" -ErrorAction SilentlyContinue) -ne $null) {
#Test if the entry matches the font file name
If ((Get-ItemPropertyValue -Name $FontName -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Fonts") -eq $FontFile.Name) {
Write-Host ('Adding' + [char]32 + $FontName + [char]32 + 'to the registry.....') -NoNewline
Write-Host ('Success') -ForegroundColor Yellow
} else {
$AddKey = $true
Remove-ItemProperty -Name $FontName -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Fonts" -Force
Write-Host ('Adding' + [char]32 + $FontName + [char]32 + 'to the registry.....') -NoNewline
New-ItemProperty -Name $FontName -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Fonts" -PropertyType string -Value $FontFile.Name -Force -ErrorAction SilentlyContinue | Out-Null
If ((Get-ItemPropertyValue -Name $FontName -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Fonts") -eq $FontFile.Name) {
Write-Host ('Success') -ForegroundColor Yellow
} else {
Write-Host ('Failed') -ForegroundColor Red
}
$AddKey = $false
}
} else {
$AddKey = $true
Write-Host ('Adding' + [char]32 + $FontName + [char]32 + 'to the registry.....') -NoNewline
New-ItemProperty -Name $FontName -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Fonts" -PropertyType string -Value $FontFile.Name -Force -ErrorAction SilentlyContinue | Out-Null
If ((Get-ItemPropertyValue -Name $FontName -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Fonts") -eq $FontFile.Name) {
Write-Host ('Success') -ForegroundColor Yellow
} else {
Write-Host ('Failed') -ForegroundColor Red
}
$AddKey = $false
}
} catch {
If ($Copy -eq $true) {
Write-Host ('Failed') -ForegroundColor Red
$Copy = $false
}
If ($AddKey -eq $true) {
Write-Host ('Failed') -ForegroundColor Red
$AddKey = $false
}
write-warning $_.exception.message
}
Write-Host
}
#Get a list of all font files relative to this script and parse through the list
foreach ($FontItem in (Get-ChildItem -Path $PSScriptRoot | Where-Object {
($_.Name -like '*.ttf') -or ($_.Name -like '*.OTF')
})) {
Install-Font -FontFile $FontItem
}
</code></pre>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-67639922219098477812021-01-22T15:12:00.001-06:002021-01-22T15:12:27.156-06:00Configuration Manager Message ID 11170 ErrorI was in the process of pushing out the Microsoft Windows 10 20H2 upgrade when I had 23 systems that errored out. They reported the error shown below. <div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3_052OlHrl9X8KWnhTcAaSpFlNpeARZ7iYhjL5GZmiYkWk-p4ypg54einiL42mdWTdNssxlQ4Kxbl5oj33xD3ROZvAFEaBKoor5-AkVw7gGYxJK5n8mosIzHWJ_ehpYW-Kb3QtRYZiL4/s1123/Screenshot+2021-01-22+150547.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="637" data-original-width="1123" height="364" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3_052OlHrl9X8KWnhTcAaSpFlNpeARZ7iYhjL5GZmiYkWk-p4ypg54einiL42mdWTdNssxlQ4Kxbl5oj33xD3ROZvAFEaBKoor5-AkVw7gGYxJK5n8mosIzHWJ_ehpYW-Kb3QtRYZiL4/w640-h364/Screenshot+2021-01-22+150547.jpg" width="640" /></a></div><br /><div>After looking at the logs and at Software Center on the target machine, I found the target machine was not seeing the deployment under Operating Systems. The fix was to reinstall the client on the target machine. Once the reinstall completed and the client synchronized with the ConfigMgr server, the deployment appeared in Software Center and successfully completed. </div>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-62378572627830607162021-01-11T17:02:00.002-06:002021-01-19T16:06:30.289-06:00Automating the Deletion of Windows.old<p>It is the beginning of 2021 and my first project for the new year is upgrading all systems to Windows 10 20H2. At the end of the upgrades comes the cleanup and there is no clean way to do this for system admins. Cleanmgr.exe is now deprecated as of Windows 10 2004. There is not a PowerShell option for controlling storage sense. Cleanmgr.exe /AUTOCLEAN would open up cleanmgr.exe and then freeze. It never deleted the folder and was stuck at zero CPU usage. I also tried using PSEXEC to execute it with the same results. Another suggestion was using task scheduler. I also tried using Dism.exe /online /Cleanup-Image /StartComponentCleanup. The Windows.old folder was still present. The next option is to delete the folder. Here is a pic of the failure to clean up the Windows.old folder using a domain admin account and cmd.exe run as administrator.</p><p>NOTE: There is an issue we ran into when someone had a USB drive connected. The system connected to the wrong drive. and installed the SMSTaskSequence on that drive instead of the bootable C: drive. This caused the system to reboot constantly because it then did not see the Windows.old directory to delete. I fixed this by adding logic that looked for the Windows.old directory. The new code is below</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHalkFLoocs2T_WycvZn9lq7Awu8W8si8jwabLqDUCUEMD37a46GDWpLSjFBVARABkBRh7zNNLe9j7Z8XKPywH-zKRUQmakjIrVid3KvEPpkdgWN2KLUdphlJejoodcVBNTUI3Z5zrxrM/s980/dism.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="708" data-original-width="980" height="462" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHalkFLoocs2T_WycvZn9lq7Awu8W8si8jwabLqDUCUEMD37a46GDWpLSjFBVARABkBRh7zNNLe9j7Z8XKPywH-zKRUQmakjIrVid3KvEPpkdgWN2KLUdphlJejoodcVBNTUI3Z5zrxrM/w640-h462/dism.jpg" width="640" /></a></div><br /><p><br /></p><p><b><span style="color: red;">NOTE:</span></b> Once this folder has been deleted, Windows cannot be reverted back to the previous version. </p><p>Once the upgrade has taken place, the Windows.old folder is present. It typically takes up 15+ GB of space. The upgraded OS is still actively using some files in the directory as I learned while exploring how to delete them. I found the files being used were drivers. This prevents you from deleting it while the OS is in memory. The alternative is to load WinPE so the OS is not in memory thereby freeing up the directory for deletion. Once the directory is deleted in WinPE and the system reboots, the OS reassociates the drivers it was using in the Windows.old directory to the Windows directory.</p><p>I first tried using PowerShell to delete the directory and there was a constant problem. PowerShell could not delete all files and directories, no matter what I tried. I consistently got the message "No mapping between account names and security IDs was done." Next, I tried RMDIR and it worked perfectly. I was able to use PowerShell to both find the drive in the WinPE environment and then execute the RMDIR command to delete the Windows.old directory on that drive. I tried this as a one-liner, but it was hit and miss with the RMDIR accepting the piped output instead of a variable. I found storing the path to the Windows.old directory in a variable was much more reliable. Here is the two-line code below:</p><pre style="background-attachment: initial; background-clip: initial; background-color: #f0f0f0; background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz1xGp9q86dTZmBT6Kgf4lTiCRnpB-883UDue4w0nS25otuiDMzqANNGCcUIMXUyHVEm0-djravLr2rh4_OiSiUlcv2KthEqRgIz1dikIsp_zoYcmSlQyrulsFHeMkbNa6Gd9iCnqBnO-K/s320/codebg.gif); background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;">$Drive = (Get-Partition | Where-Object {((Test-Path ($_.DriveLetter + ':\Windows.old')) -eq $True)}).DriveLetter
If ((Test-Path ($Drive + ':\Windows.old')) -eq $true) {
$Directory = $Drive + ':\Windows.old'
cmd.exe /c rmdir /S /Q $Directory
}
</code></pre>
<p>Here is a screenshot of the task sequence I used to deploy to the systems. The first restart is to load WinPE, the second sequence is the above PowerShell script, and the third sequence reboots the system back into the installed OS.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWuPueSu8c5CHl-1gRgyLAvPNVPkw89HmfWN8FBbgkA50m1Ft5uff2eagTDGu69ratizcLgtc539tUOg1d7O7eZkCAd9JryxhdDZIp0h5mYcfCtQ5dH5vtHetICD8wL4SsMz8Y4cFd420/s949/windowsold.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="778" data-original-width="949" height="524" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWuPueSu8c5CHl-1gRgyLAvPNVPkw89HmfWN8FBbgkA50m1Ft5uff2eagTDGu69ratizcLgtc539tUOg1d7O7eZkCAd9JryxhdDZIp0h5mYcfCtQ5dH5vtHetICD8wL4SsMz8Y4cFd420/w640-h524/windowsold.jpg" width="640" /></a></div><br /><p><br /></p>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com4tag:blogger.com,1999:blog-6159186316497589396.post-87427038662610900262020-09-25T14:32:00.001-05:002020-09-25T14:32:21.674-05:00Check Boot Environment for BIOS or UEFI<p>One of my recent projects is to convert our remaining legacy systems from BIOS to UEFI. While setting up the task sequence, I needed to be able to test the system to make sure it was not already UEFI so the task sequence would end if it was. </p><p>The PowerShell script reads the setupact.log file and extracts if it is configured as BIOS or UEFI. I have included an unknown message in the event the log file does not exist or is inaccessible, which is what I encountered on one machine. For setting it up in a Configuration Manager task sequence, I set the sequence to look for a return code of 0, else it will return either a 1 or 2, which will fail the task sequence, and allow the admin to know why it failed from the console with the error code being returned.</p><p>You can download the script from my <a href="https://github.com/MicksITBlogs/PowerShell/blob/master/BootEnvironment.ps1" target="_blank">GitHub site</a>.</p><p><br /></p>
<pre style="background-attachment: initial; background-clip: initial; background-color: #f0f0f0; background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz1xGp9q86dTZmBT6Kgf4lTiCRnpB-883UDue4w0nS25otuiDMzqANNGCcUIMXUyHVEm0-djravLr2rh4_OiSiUlcv2KthEqRgIz1dikIsp_zoYcmSlQyrulsFHeMkbNa6Gd9iCnqBnO-K/s320/codebg.gif); background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> <#
.SYNOPSIS
Check Boot Environment
.DESCRIPTION
This script reads the setupact.log file to determine if the system is configured for BIOS or UEFI.
.NOTES
===========================================================================
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.142
Created on: 9/25/2020 11:59 AM
Created by: Mick Pletcher
Filename: BootEnvironment.ps1
===========================================================================
#>
Try {
$Output = (Get-Content -Path (((Get-ChildItem -Path ($env:windir + '\Panther') -Recurse -Filter setupact.log -ErrorAction SilentlyContinue)[0]).FullName) -ErrorAction SilentlyContinue | Where-Object {$_ -like "*Detected boot environment*"}).Replace("Detected boot environment:", "~").Split("~")[1].Trim()
If ($Output -eq 'BIOS') {
Write-Output 'BIOS'
Exit 0
} elseif ($Output -eq 'UEFI') {
Write-Output 'UEFI'
Exit 1
}
} Catch {
Write-Output 'Unknown'
Exit 2
}
</code></pre>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-77819156388390906642020-09-24T10:57:00.002-05:002020-09-24T10:57:36.545-05:00Bitlocker Non-Compliance Reporting<p>As part of the suite of security tools I am writing, this will query the configuration manager SQL database for a list of machines that are not Bitlocker encrypted. There are reports in the configuration manager for this, but not everyone in my organization has access to the configuration manager console, and we wanted a detailed report sent out on a regular basis so that it is in their mailbox with high importance, which is also not available through ConfigMgr. </p><p>This tool was written to include the computer name, model, chassis, drive letter, bitlocker status, last hardware inventory scan, and last logon time. The last hardware scan and last logon time give the admins an idea as to the accuracy of the system being reported. The tool was written so that it can be used with Azure Automation or Orchestrator, and even with a scheduled task if needed. The output is formatted for a clean appearance in an outlook email. </p><p>You can download the script from my <a href="https://github.com/MicksITBlogs/PowerShell/blob/master/BitlockerEncryptionReporting.ps1" target="_blank">GitHub site</a>.</p><p><br /></p><p><br /></p>
<pre style="background-attachment: initial; background-clip: initial; background-color: #f0f0f0; background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz1xGp9q86dTZmBT6Kgf4lTiCRnpB-883UDue4w0nS25otuiDMzqANNGCcUIMXUyHVEm0-djravLr2rh4_OiSiUlcv2KthEqRgIz1dikIsp_zoYcmSlQyrulsFHeMkbNa6Gd9iCnqBnO-K/s320/codebg.gif); background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; background: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> <#
.SYNOPSIS
Bitlocker Encryption Reporting
.DESCRIPTION
This script queries the Configuration Manager SQL database for a list of machines that are not Bitlocker Encrypted. It is limited to non-desktop chassis, which can be changed by modifying the SQL query. The script is designed to output the data so that it can be used with Orchestrator, Azure Automation, or a scheduled task.
.PARAMETER SQLServer
Name of the SQL server
.PARAMETER SQLDatabase
Name of the SQL database
.NOTES
===========================================================================
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.142
Created on: 9/21/2020 3:35 PM
Created by: Mick Pletcher
Filename: BitlockerEncryptionReporting.ps1
===========================================================================
#>
[CmdletBinding()]
param
(
[ValidateNotNullOrEmpty()]
[string]$SQLServer,
[ValidateNotNullOrEmpty()]
[string]$SQLDatabase
)
$Query = "SELECT ResultTableName FROM dbo.v_Collections"
$Collection = Invoke-Sqlcmd -ServerInstance $SQLServer -Database $SQLDatabase -Query $Query
#SQL query to retrieve the list of machines
$Query = "SELECT DISTINCT Name as ComputerName, dbo.Computer_System_DATA.Model00 AS Model, dbo.System_Enclosure_DATA.ChassisTypes00 AS Chassis, dbo.ENCRYPTABLE_VOLUME_DATA.DriveLetter00 AS DriveLetter, ProtectionStatus00 AS BitlockerStatus, LastHardwareScan, ADLastLogonTime AS LastLogonTime FROM dbo.Computer_System_DATA INNER JOIN dbo.ENCRYPTABLE_VOLUME_DATA ON dbo.Computer_System_DATA.MachineID = dbo.ENCRYPTABLE_VOLUME_DATA.MachineID INNER JOIN dbo.v_GS_ENCRYPTABLE_VOLUME ON dbo.v_GS_ENCRYPTABLE_VOLUME.ResourceID = dbo.ENCRYPTABLE_VOLUME_DATA.MachineID INNER JOIN dbo._RES_COLL_SMS00001 ON dbo.ENCRYPTABLE_VOLUME_DATA.MachineID = dbo._RES_COLL_SMS00001.MachineID INNER JOIN dbo.System_Enclosure_DATA ON dbo._RES_COLL_SMS00001.MachineID = dbo.System_Enclosure_DATA.MachineID INNER JOIN dbo.v_GS_VOLUME ON dbo.System_Enclosure_DATA.MachineID = dbo.v_GS_VOLUME.ResourceID WHERE (((dbo.System_Enclosure_DATA.ChassisTypes00 = 8) OR (dbo.System_Enclosure_DATA.ChassisTypes00 = 9) OR (dbo.System_Enclosure_DATA.ChassisTypes00 = 10) OR (dbo.System_Enclosure_DATA.ChassisTypes00 = 12) OR (dbo.System_Enclosure_DATA.ChassisTypes00 = 14) OR (dbo.System_Enclosure_DATA.ChassisTypes00 = 31)) AND (dbo.ENCRYPTABLE_VOLUME_DATA.DriveLetter00 = 'C:') AND (dbo.ENCRYPTABLE_VOLUME_DATA.ProtectionStatus00 = 0)) ORDER BY Name"
$Report = Invoke-Sqlcmd -ServerInstance $SQLServer -Database $SQLDatabase -Query $Query
#If the report has no machines, then exit this script with an error code 1 so that the automation tool the link will not continue to the email task
If ($Report -ne $null) {
$Array = @()
foreach ($Item in $Report) {
$SysObj = New-Object -TypeName System.Management.Automation.PSObject
$SysObj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $Item.ComputerName
$SysObj | Add-Member -MemberType NoteProperty -Name Model -Value $Item.Model
$SysObj | Add-Member -MemberType NoteProperty -Name Chassis -Value $Item.Chassis
$SysObj | Add-Member -MemberType NoteProperty -Name DriveLetter -Value $Item.DriveLetter
$SysObj | Add-Member -MemberType NoteProperty -Name BitlockerStatus -Value $Item.BitlockerStatus
$SysObj | Add-Member -MemberType NoteProperty -Name LastHardwareScan -Value $Item.LastHardwareScan
$SysObj | Add-Member -MemberType NoteProperty -Name LastLogonTime -Value $Item.LastLogonTime
$Array += $SysObj
}
#Bitlocker reporting fields from the query
$Fields = @("Computer Name", "Model", "Chassis", "Drive Letter", "Bitlocker Status", "Last Hardware Scan", "Last Logon Time")
#Title row
$Output = ($Fields[0] + [char]9 + [char]9 + $Fields[1] + [char]9 + [char]9 + [char]9 + [char]9 + $Fields[2] + [char]9 + [char]9 + $Fields[3] + [char]9 + $Fields[4] + [char]9 + [char]9 + $Fields[5] + [char]9 + $Fields[6] + [char]13)
#Add each entry while formatting the computername column as to the width of the computername
foreach ($Item in $Array) {
If ($Item.ComputerName.Length -le 3) {
$ComputerName = $Item.ComputerName + [char]9 + [char]9 + [char]9 + [char]9 + [char]9
} elseif ($Item.ComputerName.Length -le 7) {
$ComputerName = $Item.ComputerName + [char]9 + [char]9 + [char]9 + [char]9
} elseif ($Item.ComputerName.Length -le 11) {
$ComputerName = $Item.ComputerName + [char]9 + [char]9 + [char]9
} elseif ($Item.ComputerName.Length -le 15) {
$ComputerName = $Item.ComputerName + [char]9 + [char]9
} else {
$ComputerName = $Item.ComputerName + [char]9
}
$Output += $ComputerName + $Item.Model + [char]9 + [char]9 + [char]9 + $Item.Chassis + [char]9 + [char]9 + $Item.DriveLetter + [char]9 + [char]9 + $Item.BitlockerStatus + [char]9 + [char]9 + [char]9 + $Item.LastHardwareScan + [char]9 + $Item.LastLogonTime + [char]13
}
#Write the output so it can be collected from the automation tool
Write-Output -InputObject $Output
} else {
Exit 1
}
</code></pre>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-88629811526952052132020-09-15T14:37:00.000-05:002020-09-15T14:37:33.531-05:00Preventing Windows 10 Apps from Reappearing after an In-Place or Feature UpgradeIn the imaging process I devised, most of the Windows 10 Built-In apps, such as mail and maps, are removed. My company uses the SAC edition of Windows 10 enterprise. This requires us to perform an in-place upgrade every 6 months. In order to prevent the built-in apps from reappearing, you can add specific registry keys that permanently deprovision the apps. <div><br /></div><div>The first thing you will need to do is to get a list of the apps you want to deprovision. To do so, execute the following PowerShell one-liner to get a list of the apps with the display name on the right with the associated package name on the left. The package name is what will be needed. </div><div><br /></div>
<pre style="background: rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"> Get-AppxProvisionedPackage -Online | Select DisplayName, PackageName | Sort-Object -Property DisplayName | Export-Csv -Path Apps.txt -Force -Encoding UTF8 -NoTypeInformation
</code></pre>
<br />
It is best to edit the CSV file in excel. Once you open the generated text file up, delete those items you do <span style="color: red;">NOT</span> want the OS to remove, and then delete the DisplayName column. The CSV file is now ready to be used with the PowerShell script. You will need to rerun the above PowerShell script each time an OS upgrade is available and has been installed to get any new additions. <div><br /></div><div>The script will read the contents of the text file and then adds those as a registry key located under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Appx\AppxAllUserStore\Deprovisioned. You can find out more information on the registry keys and how they work <a href="https://docs.microsoft.com/en-us/windows/application-management/remove-provisioned-apps-during-update#registry-keys-for-provisioned-apps" target="_blank">here</a>. </div><div><br /></div><div>The following script will only need to be executed once. You can download it from my <a href="https://github.com/MicksITBlogs/PowerShell/blob/master/Windows10AppDeprovisioning.ps1" target="_blank">GitHub site</a>. </div><div><br /></div><div><br /></div>
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz1xGp9q86dTZmBT6Kgf4lTiCRnpB-883UDue4w0nS25otuiDMzqANNGCcUIMXUyHVEm0-djravLr2rh4_OiSiUlcv2KthEqRgIz1dikIsp_zoYcmSlQyrulsFHeMkbNa6Gd9iCnqBnO-K/s320/codebg.gif); background: rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"> <#
.SYNOPSIS
Deprovision Specified Windows 10 Apps
.DESCRIPTION
This script will add the appropriate registry keys to deprovision the built-in Windows 10 applications specified in the associated text file, or hardcoded in the script.
.PARAMETER AppListFile
File containing list of Windows 10 files to deprovision
.NOTES
===========================================================================
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.142
Created on: 9/15/2020 9:56 AM
Created by: Mick Pletcher
Filename: Windows10AppDeprovisioning.ps1
===========================================================================
#>
[CmdletBinding()]
param
(
[string]$AppListFile
)
#Get list of Windows 10 Applications to uninstall from text file
$Applications = Get-Content -Path $AppListFile
#Deprovisioned Registry Key
$RegKey = 'REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Appx\AppxAllUserStore\Deprovisioned'
#Create deprovisioned registry key if it does not exist
If (!(Test-Path $RegKey)) {
New-Item -Path $RegKey -Force | Out-Null
}
#Add list of Apps from the imported text file to the deprovisioned registry key
foreach ($App in $Applications) {
#Install registry key if it does not exist
If (!(Test-Path ($RegKey + '\' + $App))) {
New-Item -Path ($RegKey + '\' + $App) -Force | Out-Null
}
}
</code></pre>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-52617036511822289212020-09-09T11:40:00.000-05:002020-09-09T11:40:39.331-05:00Remotely Pushing Windows Updates via Command Line to Windows 10 Machines<p>Normally, windows updates are pushed to machines using Configuration Manager in an enterprise environment. There are occasions though when they must manually be pushed, such as when a system continues to fail via ConfigMgr and troubleshooting is required. The first tool I use is the <a href="https://www.powershellgallery.com/packages/PSWindowsUpdate/2.2.0.2" target="_blank">PSWindowsUpdate PowerShell module</a>. This allows me to remotely push updates via PowerShell and <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/psexec" target="_blank">PSEXEC</a>, in which I can watch the results. </p><p>To use the PSWindowsUpdate module, it must be installed first. I do so by installing the module on all machines during the build process, but it can also be pushed through ConfigMgr or you can use PSEXEC.exe to install it. The command line I use is:</p><div><pre style="background: rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"> powershell.exe -executionpolicy bypass -command "&{Install-Module -Name PSWindowsUpdate -Force -Confirm:$false}"
</code></pre>
</div>
<br /><div>Once it is installed on the remote machine, I use the following PowerShell one-liner in the configuration manager Scripts section to remotely execute it. </div><div><br /></div><pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz1xGp9q86dTZmBT6Kgf4lTiCRnpB-883UDue4w0nS25otuiDMzqANNGCcUIMXUyHVEm0-djravLr2rh4_OiSiUlcv2KthEqRgIz1dikIsp_zoYcmSlQyrulsFHeMkbNa6Gd9iCnqBnO-K/s320/codebg.gif); background: rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"> Install-WindowsUpdate -IgnoreReboot -AcceptAll;Start-Process -FilePath ($env:windir + '\system32\UsoClient.exe') -ArgumentList 'ScanInstallWait StartInstall'
</code></pre>
The issue I have seen with just using the PSWindowsUpdate module with 1903 and later versions of Windows 10 is that it will trigger the updates to download, but will not always install them, especially feature updates. The UsoClient.exe will trigger the updates to install. This command-line executable is the same as going to the Check for Windows Updates screen and clicking the button to install updates. You can find more info on UsoClient <a href="https://www.urtech.ca/2018/11/usoclient-documentation-switches/" target="_blank">here</a>. <div><br /></div><div>If you are trying to get a feature update to install remotely, you will need to execute UsoClient.exe ScanInstallWait StartInstall at least a couple of times before it starts to install after the update has been downloaded. </div>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-21170197875107991382020-09-01T13:37:00.002-05:002020-09-01T13:40:54.683-05:00AD Group Member Reporting<p>This tool queries specified AD groups for new users that have been added to the group within a specified number of days. The script is written so that it can be used with Azure Automation, Orchestrator, or even a scheduled task, with the addition of the send-mailmessage cmdlet. The intent of this is to track if say a user needs to be temporarily added to the domain admins group for software installation, but then the help desk forgets to remove them. It can also be a tracking tool in the event a cyber-incursion is happening and a compromised account gets added to that group. Obviously, if a user who is already in that group is compromised, then this script does not impact that type of event. This script was made possible by using the <a href="https://www.powershellbros.com/check-when-user-was-added-to-ad-group/" target="_blank">function posted over at PowerShellBros</a> with a few modifications to it.</p><p>The script exits with a write-output statement if any new users appear in the report. The Write-Output allows for the data to be sent to the next step in say Orchestrator. If no new users appear, then the script exits with an error code 1. This is so the link in Azure Automation or Orchestrator can be set to only proceed to send an email if there was an error code 0, which would be the Write-Output statement. </p><p>You can download the script from my GitHub repository located <a href="https://github.com/MicksITBlogs/PowerShell/blob/master/ADGroupUserInfo.ps1" target="_blank">here</a>. </p><p><br /></p>
<pre style="background: rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"> <#
.SYNOPSIS
AD Security Group User Additions
.DESCRIPTION
This script retrieves a list of users added to a specified AD security group, which includes the date it was last modified. This info can be used to track whether a new user has been recently added to a security group, especially a group that elevates priviledges. This can be used as a tool to help fight cyber-crime.
.PARAMETER NumberOfDays
Number of days to look back for users having been added to the designated AD security group
.PARAMETER SecurityGroup
Name of the AD security group
.NOTES
===========================================================================
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.142
Created on: 9/1/2020 11:03 AM
Created by: Mick Pletcher
Filename: ADGroupUserInfo.ps1
===========================================================================
#>
[CmdletBinding()]
param
(
[ValidateNotNullOrEmpty()]
[int]$NumberOfDays,
[ValidateNotNullOrEmpty()]
[string]$SecurityGroup
)
Function Get-ADGroupMemberDate {
[cmdletbinding()]
Param (
[parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True, Mandatory = $True)]
[string]$Group
)
Begin {
[regex]$pattern = '^(?<State>\w+)\s+member(?:\s(?<DateTime>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})\s+(?:.*\\)?(?<DC>\w+|(?:(?:\w{8}-(?:\w{4}-){3}\w{12})))\s+(?:\d+)\s+(?:\d+)\s+(?<Modified>\d+))?'
$DomainController = ($env:LOGONSERVER -replace "\\\\")
If (!$DomainController) {
Throw "Computer from which script is run is not joined to domain and domain controller can not be identified."
Break
}
}
Process {
Write-Verbose "Checking distinguished name of the group $Group"
Try {
$distinguishedName = (Get-ADGroup -Identity $Group).DistinguishedName
} Catch {
Write-Warning "$group can not be found!"
Break
}
$RepadminMetaData = (repadmin /showobjmeta $DomainController $distinguishedName | Select-String "^\w+\s+member" -Context 2)
$Array = @()
ForEach ($rep in $RepadminMetaData) {
If ($rep.line -match $pattern) {
$object = New-Object PSObject -Property @{
Username = [regex]::Matches($rep.context.postcontext, "CN=(?<Username>.*?),.*") | ForEach-Object {
$_.Groups['Username'].Value
}
LastModified = If ($matches.DateTime) {
[datetime]$matches.DateTime
} Else {
$Null
}
DomainController = $matches.dc
Group = $group
State = $matches.state
ModifiedCounter = $matches.modified
}
$Array += $object
}
}
Return $Array
}
End {
}
}
$Users = Get-ADGroupMemberDate -Group $SecurityGroup | Select Username, LastModified
$NewUsers = @()
#Find users added to the AD Group within the designated number of days
Foreach ($User in $Users) {
If ((New-TimeSpan -Start $User.LastModified -End (Get-Date)).Days -lt $NumberOfDays) {
$NewUsers += $User
}
}
If ($NewUsers.Count -gt 0) {
Write-Output $NewUsers
} else {
Exit 1
}
</code></pre>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0tag:blogger.com,1999:blog-6159186316497589396.post-56461305436124614492020-08-28T12:09:00.001-05:002020-08-28T12:09:30.565-05:00Multiple Machine Logon ReportingThis tool is designed to report if a user profile has been logged into a defined number of machines or greater over a specified period of time. The purpose of the tool is to:<div style="text-align: left;"><ul style="text-align: left;"><li>Detect a cybercriminal who may have gotten one user credential and is now using it to probe machines across the domain</li><li>Detect a disgruntled employee who may be logging into multiple machines for destructive reasons</li></ul><div>The tool works by querying the ConfigMgr SQL server for a list of machines users have logged into. This list comes from the primary devices association. Every user that logs into a machine is added to this list. The list that appears in the GUI of ConfigMgr does not display every login that is stored in the SQL database. I am not sure of the timeframe in which systems disappear from the GUI list. </div><div><br /></div><div>If you are interested in just the SQL Query, here it is. The only things that should need to be changed are the -14 and 4. The -14 is the number of days back you want the report to be run and the 4 is the number of machines a user can log in to without appearing on the report.</div><div><br /></div>
<pre style="background: rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"> SELECT UniqueUserName, COUNT(UniqueUserName) as Logins FROM dbo.v_UserMachineRelationship WHERE dbo.v_UserMachineRelationship.CreationTime >= DATEADD(day,-14, GETDATE()) GROUP BY UniqueUserName HAVING (COUNT(UniqueUserName) > 4) ORDER BY Logins DESC
</code></pre>
<div><br /></div><div>This is the PowerShell script that connects to the SQL server to retrieve the list. The script creates a simple list of users and the number of machines they logged into and outputs that in pure text. This can be used in Microsoft Orchestrator or Azure Automation to automatically generate a report. It also generates a detailed report in a CSV file that can be attached to the same automatic email if the recipients want to look at the details that show every machine logged into along with the date stamp. If there are no users to report, the script deletes the last saved CSV file and exits with an error code 1. The purpose of error code one was for the Orchestrator link. The link checks the return code and only proceeds to the email activity if there was an error code 0 returned. As a note for Orchestrator, I have the script run on the SQL server so the PowerShell cmdlets are present. </div></div>
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz1xGp9q86dTZmBT6Kgf4lTiCRnpB-883UDue4w0nS25otuiDMzqANNGCcUIMXUyHVEm0-djravLr2rh4_OiSiUlcv2KthEqRgIz1dikIsp_zoYcmSlQyrulsFHeMkbNa6Gd9iCnqBnO-K/s320/codebg.gif); background: rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"> <#
.SYNOPSIS
SCCM Endpoint Report
.DESCRIPTION
This script is intended to be used to detect if a user logs into multiple machines within a designated time period, which can be a sign of an intruder.This script will query Configuration Manager using the designated Number of machines and how many days back for a list of machines that have logged into more than the designated number. It will return a list of machines using Write-Output so the output can also be used in Orchestrator or SMA. The list it returns is a simple list that displays the username and the number of machines it was logged into. There is also a more detailed list generated that contains the list of all machines it was logged into. This list can be attached to an email to be sent with the simple list if desired.
.PARAMETER SQLServer
Name of the SQL server
.PARAMETER SQLDatabase
Name of the SQL database
.PARAMETER NumberOfMachines
Maximum number of machines a user can login to before appearing on this report
.PARAMETER NumberOfDays
This is a string value because it requires a negative value for the number of days you want to report to go back.
.PARAMETER Report
This is the specified name of the detailed user login report to be generated. This must include both the full path and .csv as part of the name.
.NOTES
===========================================================================
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.142
Created on: 8/19/2020 3:58 PM
Created by: Mick Pletcher
Filename: UserLoginReport.ps1
===========================================================================
#>
[CmdletBinding()]
param
(
[ValidateNotNullOrEmpty()]
[string]$SQLServer,
[ValidateNotNullOrEmpty()]
[string]$SQLDatabase,
[ValidateNotNullOrEmpty()]
[int]$NumberOfMachines,
[ValidateNotNullOrEmpty()]
[string]$NumberOfDays,
[ValidateNotNullOrEmpty()]
[string]$Report = 'c:\UserLoginDetailedReport.csv'
)
$SimpleReport = Invoke-Sqlcmd -ServerInstance $SQLServer -Database $SQLDatabase -Query ("SELECT UniqueUserName, COUNT(UniqueUserName) as Logins FROM dbo.v_UserMachineRelationship WHERE dbo.v_UserMachineRelationship.CreationTime >= DATEADD(day," + $NumberOfDays + ", GETDATE()) GROUP BY UniqueUserName HAVING (COUNT(UniqueUserName) > " + $NumberOfMachines + ") ORDER BY Logins DESC")
If ($SimpleReport -ne $null) {
$DetailedReport = @()
foreach ($User in $SimpleReport.UniqueUserName) {
$DetailedReport += Invoke-Sqlcmd -ServerInstance $SQLServer -Database $SQLDatabase -Query ("SELECT UniqueUserName, MachineResourceName, CreationTime FROM dbo.v_UserMachineRelationship WHERE UniqueUserName = " + [char]39 + $User.Trim() + [char]39 + " AND dbo.v_UserMachineRelationship.CreationTime >= DATEADD(day," + $NumberOfDays + ", GETDATE()) ORDER BY CreationTime ASC")
}
$DetailedReport | Export-Csv -Path $Report -Force -Encoding UTF8 -NoTypeInformation
Write-Output $SimpleReport
} else {
Remove-Item -Path $Report -Force -ErrorAction SilentlyContinue
Exit 1
}
</code></pre>Mick Pletcherhttp://www.blogger.com/profile/09597651323688418212noreply@blogger.com0