How to use SplitPipeline with o365 cmdlets - powershell

I have been trying to check the mailboxes for a specific list of users (about 14k users) because it is possible that a malicious rule has been created.
I created a very simple PowerShell script to achieve that but obviously it took more than 12 hours to finish. I have found a multi-threading option to process all this data: SplitPipeline, but I don't even know how to start.
After installing and importing the module and logging into O365, I'm trying this (please not that I'm just starting in this powershell world, I'm not an expert):
$file = Get-Content userlist.txt
$outputFile = "outputFile.txt"
$data = #{
Count = $file.Count
Done = 0
}
$file | Split-Pipeline -Count 10 -Variable data {process{
[System.Threading.Monitor]::Enter($data)
try
{
$done = ++$data.Done
}
finally
{
[System.Threading.Monitor]::Exit($data)
}
Write-Progress -Activity "Done $done" -Status Processing -PercentComplete (100*$done/$data.Count)
$User2Check = $_
Write-Host "Checking $User2Check"
get-mailbox $User2Check | Select identity
}} | Set-Content $outputFile
The result is the typical message: The term 'get-mailbox' is not recognized as the name of a cmdlet, it is like SplitPipeline is not aware I'm logged against O365.
Do you have any ideas?

You have to understand how Split-Pipeline works, it runs in its own workspace. Unfortunately one cannot call/use functions and variables specified outside split-pipeline run space. You can use the -Variable -Function switches provided by Split-pipeline, but keep in mind that in the case of functions, any global variable used by xyz function you are trying to call cannot see the global variable.
Indoor case, get-mailbox is null because Split-pipe does not know about it, you will have to include the model and create a new connection to office in order for the get-mailbox cmdlet to work.

Related

How do you delete user profiles in powershell?

I am writing a powershell script to delete user profiles and I understand the method I am using is not the best. I was wondering what would be a better way to do it? I am still very much new to powershell but I am willing to learn
The code I already have:
$ErrorActionPreference= 'silentlycontinue'
$Users = Get-WmiObject -Class Win32_UserProfile
$IgnoreList = "helpdesk", "administrator", "Default"
:OuterLoop
foreach ($User in $Users) {
foreach ($name in $IgnoreList) {
if ($User.localpath -like "*\$name") {
continue OuterLoop
}
}
$User.Delete()
}
WHy script this when the enterprise approach is GPO.
How to Delete Old User Profiles Using GPO and PowerShell?
No reason to do this from scratch, leverage what others have provided...
Use PowerShell to remove local profiles
How to delete user profiles older than a specified number of days in Windows
...as well as the modules from the MS powershellgallery.com, as you look at whatever approach you decide use.
Find-Module -Name '*user*profile*' | Format-Table -AutoSize
<#
# Results
Version Name Repository Description
------- ---- ---------- -----------
1.0 UserProfile PSGallery This module manages user profiles on local and remote computers
0.1.1 Microsoft.Graph.Users.ProfilePhoto PSGallery Microsoft Graph PowerShell Cmdlets
1.0.6 Get-UserProfile PSGallery The Get-UserProfile module list or remove User Profiles from local
#>
Find-Module -Name '*userprofile*' | Format-List -Force
Update
Yet, you specifically said...
'I understand the method I am using is not the best. I was wondering
what would be a better way to do it?
... and what we all have suggested, using GPO is the best way, the normal industry-accepted enterprise way to do this. Don't script, unless you have no other choice. Windows AD will do this for you.
Don't reinvent the wheel unless you know it's really a better wheel. In learning, of course, there is study, trial, and error, but learn and use from sources that have already done this. There are tons of examples all over the web for this use case. Just search for it. No reason to do this from scratch.
'powershell remove user profiles'
Which are showing what you are already doing... Example(s) - pre-built scripts for this use case via the Ms powershellgallery.com.
Use PowerShell delete a user profile (step-by-step guide)
Get-CimInstance -ComputerName SRV1,SRV2,SRV3 -Class Win32_UserProfile |
Where-Object { $_.LocalPath.split('\')[-1] -eq 'UserA' } |
Remove-CimInstance
Remove-UserProfile - Remove Local User Profiles and Clean C:\Users Directory
This script contains a function (Remove-UserProfile) which is used to
remove user profiles, and additional contents of the C:\Users
directory (if specified) on a local computer.
Download: Remove-UserProfile.ps1
Delete Unused user Profiles on local machine (PowerShell)
Script Delete user profiles over multiple servers v2
ANd using the modules that you see from the above commands, is not something to do later in life. Those are in / available from MS and in PowerShell directly for a reason. Everything you are using in PowerShell is coming from modules hosted on your machine and the ones you download and install from MS and other resources.
Again, use the built-in enterprise tools in Windows or other chosen OS as designed, and if they don't provide what you need, then look to other options, like scripting to an object-level that the enterprise tool is not exposing in its GUI.
I do a similar thing. With a lot of profiles, I've found I've had to wait for the cpu to calm down because of the appxsvc service spawning threads without limit. I used to delete per-user firewall rules, but that doesn't seem necessary anymore.
$excludedprofilelist = 'C:\Users\admin1','C:\users\admin2'
$myprofiles = $profiles | where { !$_.Special -and
$excludedprofilelist -notcontains $_.LocalPath }
$sleepseconds = 1
$numcores = 4
foreach ($profile in $myprofiles) {
$msg = "deleting profile " + $profile.LocalPath
$profile | remove-wmiobject
$msg = $msg + " $?"
echo $msg # the result
# recycle bin
if (test-path c:\`$recycle.bin\$($profile.sid)) {
rm -r c:\`$recycle.bin\$($profile.sid) -force
}
# wait for appx cleanup, what if profile delete error?
#while ( (get-appxpackage -user $profile.sid).count ) {
# sleep 1
#}
do {
# make sure it's running
$id = Get-WmiObject -Class Win32_Service -Filter "Name = 'appxsvc'" |
Select-Object -ExpandProperty ProcessId
# $proc = get-process -id $id
# access is denied
# 4proc.priorityclass = 'belownormal'
$cpu1 = (get-process -Id $id).cpu
sleep $sleepseconds
$cpu2 = (get-process -Id $id).cpu
$cpu = [int](($cpu2 - $cpu1)/($numcores*$sleepseconds) * 100)
} while ($cpu)
}

Printer Migration - Powershell script

I have found some great examples on foreach loops in Powershell here but I just can't wrap my head around foreach loops for what I am doing.
I found great scripts that deal with migrating printer when migrating from one Windows print server to another however my challenge is that I am migrating from an Novell iPrint server to a Windows server.
The struggle is that the printer name or share name (or any printer property) for iPrint printer is not the hostname so I have to come up with some translation table with iPrint name and Printer hostname.
Initially, I wanted to just have column 2 of my translation table have it execute my powershell command to install a network printer which would make things easier.
I am in the process of trying to create a logon script to query printers that are installed on computer and have it do a 'foreach' loop against a CSV with iPrint names and hostnames.
csv 1
installediprintprintername1
installediprintprintername2
installediprintprintername3
printtranslationtable.csv
column 1 column 2
iprintprintername1 hostnameprinter1
iprintprintername2 hostnameprinter2
iprintprintername3 hostnameprinter3
iprintprintername4 hostnameprinter4
This is what I got so far but not able to get it to work. Any help would be appreciated!
$printers = #(Get-wmiobject win32_printer)
$path = "\\networkdrive\printtranslationtable.csv"
$printertranslation = Import-Csv -path $path
foreach ($iprintprinter in $printtranslationtable) {
foreach ($name in $csv1) {
if ($name -eq $printtranslationtable.column1) {
Write-Host $newPrinter = $printtranslationtable.column2
}
}
}
Update
So I was able to tweak the script #TheMadTechnician suggested and able to get this PS script to work in my environment. What I am trying to do is to check if new printers are installed and if they are then just exit script. This is what I have but can't get it to exit or break. I was also trying to write the new printers into text file but not necessary, I would like for it to stop executing script.
if (($printers.name -like "\winprint*") -eq $true) {
$printers.name -like "\winprint\" | out-file -FilePath "C:\windowsprinters.txt" -Append
{break} {exit}
}
When you read the file with Import-Csv, PowerShell creates an array of custom objects with property names from the header line. On the other hand Get-Content produces simple array of string values. I came up with this one liner, which goes thru the translation table and checks if the printer list contains one. This is not optimal if you have billions of printers, but keeps things clear:
printers.txt:
iprinter2
iprinter3
printertable.csv:
"Column1";"Column2"
"iprinter1";"hostname1"
"iprinter2";"hostname2"
"iprinter3";"hostname3"
"iprinter4";"hostname4"
PowerShell:
$printers = Get-Content .\printers.txt
$prtable = Import-Csv -Delimiter ";" .\printertable.csv
$prtable | ?{ $printers -contains $_.Column1 } | %{Write-Host "Install $($_.Column2)"}
Ok, so you query what printers are installed, and you have a translation table loaded from a CSV, now you just need to look at that translation table and cross reference which entries have a listing in the local computer's printer listings.
$printers = #(Get-wmiobject win32_printer)
$path = "\\networkdrive\printtranslationtable.csv"
$printertranslation = Import-Csv -path $path
$printertranslation | Where{$_.Column1 -in $printers.ShareName} | ForEach{ Add-Printer $_.Column2 }
I don't know what property of the win32_printer object aligns best for you, but I would suggest ShareName or DeviceId. Those should be something like:
ShareName: XeroxColor02
DeviceId: \\printserver\XeroxColor02

Remove MSMQ Queue Permissions

I want to remove all of the permissions on an MSMQ queue before we set the new permissions, this will be deployed via Octopus.
This is so that we can be sure that no legacy permissions can exist and be sure that the permissions will be the same.
$QueueName = "MyQueue"
$QueuePermissions = Get-MsmqQueue -Name $QueueName | Get-MsmqQueueACL
$QueueUsers = $QueuePermissions.AccountName | Get-Unique
foreach ($User in $QueueUsers)
if ($User -like 'MyDomain*'){
#Something like
$QueueName | Set-MsmqQueueACL -UserName $User -Remove
}
Unfortunately I need to create a CSV list of permissions for Set-MsmqQueueACL to be removed.
How can I get this?
I'm fairly new to PowerShell so anyhelp would be appreciated.
Thanks!
First of all, delete queues and recreate is the more reliable approach.
I assume you have a reason that requires you to not delete them. Here is an approach I think suit you best.
Using MessageQueue.ResetPermissions Method from System.Messing.MessageQueue
Code example for powershell:
$QueueName = ".\private$\MyQueue"
Add-Type -AssemblyName System.Messaging
$Q = [System.Messaging.MessageQueue]($QueueName)
$Q.ResetPermissions()
Note: This method put queue permission back to default where only creator has full access to the queue. My powershell is using an automation account that created these queues thus it would take away fine from this point. However, in my past experience if all the queue permissions are messed, and you don't have an account that have full control of the queue, you might end up have to remove the physical queue file from storage and restart MSMQ service to clean it up. Thus I'd urge you to maintain consistence of the permission so your later operations on the queue can be performed without problem.
I have created a solution that appears to work, as mentioned above I am no PowerShell expert but it may help someone else in the future:
$Queue= "MyQueueName"
#remove old permissions
$QueuePermissions = Get-MsmqQueue -Name $Queue | Get-MsmqQueueACL
$QueueUsers = $QueuePermissions.AccountName | Get-Unique
foreach ($User in $QueueUsers)
{
Write-Output "Permissions found for user: $User"
$tst = $QueuePermissions | where {$_.AccountName -eq $User}
$tst = $tst | Select -ExpandProperty Right
foreach ($Permission in $tst)
{
Write-Output "Removing permissions: $Permission"
$thisQueue | Set-MsmqQueueAcl -UserName $User -Remove $Permission | Out-Null
}
}

exporting Powershell Script to CSV

We are getting ready to merge our AD with another. We have about 300 computers that I'm trying to match up with who uses them so the accounts and home folders migrate correctly, and I'm trying to think of the most efficient way to get this information.
We have everyone in an inventory system (Filemaker) (and will be implementing SCCM once we migrate (thank god) ) but we had a few errors when we did our first test batch. Im looking for something I can push out through group policy (possibly?) that will give me the computer name, logged in account, and them email it to me.
So far this is what I have.
[System.Environment]::UserName
[System.Environment]::UserDomainName
[System.Environment]::MachineName
Out-File T:\TEST.txt
But the output is blank. Any idea what I'm doing wrong here? Also is there a way to have this run on multiple computers but write to the same file?
"$env:USERNAME,$env:USERDOMAIN,$env:COMPUTERNAME" | Out-File 'T:\test.txt'
will write the name and domain of the currently logged-in user as well as the hostname of the local computer to the file T:\test.txt.
Using a single file may cause conflicts due to concurrent write attempts, though. It's better to use one file per computer, like this:
"$env:USERDOMAIN\$env:USERNAME" | Out-File "T:\$env:COMPUTERNAME.txt"
Run it as a logon script (or from a logon script), e.g. like this:
powershell -ExecutionPolicy Bypass -File "\\%USERDNSDOMAIN\netlogon\your.ps1"
Get-ADComputer -Filter * -Property * | Select-Object Name | Out-File C:\outdir\machinelist.txt -NoTypeInformation -Encoding UTF8
will get you all machine names, unless you have them already. Either way, use your list of machines in
$MachineList = Get-Content -Path c:\outdir\machinelist.txt;
foreach ($Machine in $MachineList){
($Machine + ": " + #(Get-WmiObject -ComputerName $Machine -Namespace root\cimv2 -Class Win32_ComputerSystem)[0].UserName) | Out-File "C:\outdir\result.txt" -Append
}
If you change the destination directory to somewhere that all computers have access to, it can run on multiple computers. It won't email it to you but you can just grab it.
You'll need to pipe those properties into the file like..
[System.Environment]::UserName, [System.Environment]::UserDomainName, [System.Environment]::MachineName | Out-File T:\Test.txt

PowerShell 3.0 - Setting Affinity to CPU per USER's PROCESS

my first post here. I am working on a script using powerShell, the objective is to set a certain amount of CPU-threads per USER's process, using the forum here, i was able to find most of the answers, and even got my script to run, except, if it sets the affinity, it sets it to EVERY-Process, not just the user i need.
here is the code(with comments):
# GET LIST of all process running
$pList = get-wmiobject win32_process
# loop through created array and get the OWNER of the processes
foreach ($p in $pList) {
#If "myUserName" is found:
if ($p.getowner().User -eq 'myUserName') {
# get process name
$procName = $p.ProcessName
# trim STRING to remove EXE
$procName = $procName.Replace('.exe','')
# use get-process to make array of processes run by "myUserName"
$activeProc = Get-Process -name $procName
# Loop to set affinity for each process
foreach ($i in $activeProc){
$i.ProcessorAffinity=0xFE
}
}
}
when i execute this command, all of the process are set to new Thread Count,
any suggestions how to make it ONLY adjust threads for SPECIFIC user?
Thanks a lot guys!
this is pretty urgent.
By calling get-process -name $procName you are finding all processes that have the same name as one run by the user.
Instead of using the ProcessName, use ProcessId.
In PowerShell version 4.0, you can use the -IncludeUserName parameter on the Get-Process cmdlet. Once you have a list of processes, you can then filter then using the Where-Object cmdlet, which has a default alias of ?.
Get-Process -IncludeUserName | Where-Object -FilterScript { $PSItem.UserName -match 'system' };
Or short-hand might look like this:
gps -inc | ? { $_.UserName -match 'system' };
Note: Using the -IncludeUserName parameter requires privilege elevation.