Recent Discussions
Connecting to multiple Microsoft services with the same session
Hi guys. Working on a script that needs to connect to ExchangeOnlineManagement, TeamsOnlineManagement, SharePointOnlineManagement.... The script will be used across many different tenants, and I also plan to make it publicly available, so 1) I don't really want to pre-configure some complicated key setup and 2) I don't really want to have login pop-ups over and over again... For ExchangeOnline, I learned (accidentally), if I do this: $upn = Read-Host -Prompt "input yer wahawha" Connect-ExchangeOnline -userprimaryname $upn Connect-IPPSsession -userprimaryname $upn And login to MY tenant, I don't get prompted for login. I think likely because my device is Entra-joined, and it's using my Microsoft account. But even if I use a different account, it will only prompt me once - reusing it for the other. This is great, and exactly how I wanted things to flow - but now I'm trying to do Connect-SPOService (sharepoint) and Connect-MicrosoftTeams... and while both of these are part of the tenant, they don't take the -userprimaryname param - so I can specify to use the account I'm logged into my PC with.. The end-goal is to have this script run with minimal user input. I've SORT OF found a workaround for SharePoint, where I can get the SharePointSite from ExchangeOnline, then modify it a bit and use it as input for Connect-SPOService... but Teams, while it doesn't have the URL param requirement, DOES prompt me to login again. Is there a way to use the existing session for either of these, like I've done with ExchangeOnline / IPPSSession? We have MFA enabled, though not required from within our company network - but when I try to use Get-Credential, it errors me out because it wants MFA.13Views0likes0CommentsWhen creating a new team from a template with powershell add new private channel and members
Hi All, I have a powershell script I am using to create and populate new teams from a template and add owners and users via .csv, Everything seem to work fine except the private team in the template is not copied to the new teams. Is there a way to copy the private team with its members from the template? if not how can I add a new private team and add users from a .csv file to my existing script. Import-Module Microsoft.Graph.Teams Connect-MgGraph -Scope Group.ReadWrite.All Connect-MicrosoftTeams $ProgressPreference = 'SilentlyContinue' ######################### #Variable definition: $DefaultModelTeam = "Team template ID" $MembersFilePath = "C:\Users\t130218\Desktop\owlimport_365.csv" $OwnersFilePath = "C:\Users\t130218\Desktop\TeamOwners.csv" ######################### Function CreaTeam{ param( [Parameter(Position=0)] [string]$displayName, [Parameter(Position=1)] [string]$description ) begin{ $params = @{ partsToClone = "apps,tabs,settings,channels" displayName = $displayName description = $description mailNickname = $displayName #visibility = "public" } #Disable "Crea" button in order to avoid duplicate Teams creation $btnCrea.enabled=$false #Message output and waiting time countdown for allow new Tean creation finalization $lblMessaggio.text="Creazione Team in corso..." $teamId= $txtTemplate.text Copy-MgTeam -TeamId $teamId -BodyParameter $params $lblTeamId.text = "Attendere 20 secondi" Start-Sleep -Seconds 5 $lblTeamId.text = "Attendere 15 secondi" Start-Sleep -Seconds 5 $lblTeamId.text = "Attendere 10 secondi" Start-Sleep -Seconds 5 $lblTeamId.text = "Attendere 5 secondi" Start-Sleep -Seconds 5 #The Teamid of the team that was just created can only be discovered via Team name search $newTeam= Get-MgGroup | Where-Object {$_.DisplayName -like $displayName} $lblTeamId.text=$newTeam.Id #Get Team members from the CSV $TeamUsers = Import-Csv $MembersFilePath -delimiter ";" #Iterate through each row obtained from the CSV and add to Teams as a Team member $TeamUsers | ForEach-Object { Add-TeamUser -GroupId $newTeam.id -User $_.m365_email -Role Member Write-host "Added User:"$_.m365_email -f Green } #Get Team owners from the CSV $TeamOwners = Import-Csv $OwnersFilePath -delimiter ";" #Iterate through each row obtained from the CSV and add to Teams as a Team member $TeamOwners | ForEach-Object { Add-TeamUser -GroupId $newTeam.id -User $_.m365_email -Role Owner Write-host "Added Owner:"$_.m365_email -f Green } } } Add-Type -AssemblyName System.Windows.Forms [System.Windows.Forms.Application]::EnableVisualStyles() $CorsoTeams = New-Object system.Windows.Forms.Form $CorsoTeams.ClientSize = New-Object System.Drawing.Point(1200,575) $CorsoTeams.text = "Corso Teams - Crea Struttura" $CorsoTeams.TopMost = $false $lblNomeCorso = New-Object system.Windows.Forms.Label $lblNomeCorso.text = "Nome del corso" $lblNomeCorso.AutoSize = $true $lblNomeCorso.width = 25 $lblNomeCorso.height = 10 $lblNomeCorso.location = New-Object System.Drawing.Point(40,79) $lblNomeCorso.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10) $btnCrea = New-Object system.Windows.Forms.Button $btnCrea.text = "Crea" $btnCrea.width = 150 $btnCrea.height = 67 $btnCrea.location = New-Object System.Drawing.Point(373,298) $btnCrea.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',16) $btnChiudi = New-Object system.Windows.Forms.Button $btnChiudi.text = "Chiudi" $btnChiudi.width = 150 $btnChiudi.height = 67 $btnChiudi.location = New-Object System.Drawing.Point(628,298) $btnChiudi.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',16) $lblDataCorso = New-Object system.Windows.Forms.Label $lblDataCorso.text = "Data del corso" $lblDataCorso.AutoSize = $true $lblDataCorso.width = 25 $lblDataCorso.height = 10 $lblDataCorso.location = New-Object System.Drawing.Point(39,143) $lblDataCorso.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10) $lblDescrizione = New-Object system.Windows.Forms.Label $lblDescrizione.text = "Descrizione (facoltativa)" $lblDescrizione.AutoSize = $true $lblDescrizione.width = 25 $lblDescrizione.height = 10 $lblDescrizione.location = New-Object System.Drawing.Point(39,210) $lblDescrizione.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10) $txtDataCorso = New-Object system.Windows.Forms.TextBox $txtDataCorso.multiline = $false $txtDataCorso.width = 150 $txtDataCorso.height = 40 $txtDataCorso.enabled = $true $txtDataCorso.location = New-Object System.Drawing.Point(370,134) $txtDataCorso.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',20) $txtNomeTeam = New-Object system.Windows.Forms.TextBox $txtNomeTeam.multiline = $false $txtNomeTeam.width = 405 $txtNomeTeam.height = 40 $txtNomeTeam.enabled = $true $txtNomeTeam.location = New-Object System.Drawing.Point(370,75) $txtNomeTeam.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',20) $txtDescrizione = New-Object system.Windows.Forms.TextBox $txtDescrizione.multiline = $false $txtDescrizione.width = 405 $txtDescrizione.height = 40 $txtDescrizione.enabled = $true $txtDescrizione.location = New-Object System.Drawing.Point(370,210) $txtDescrizione.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',20) $btnChiudi = New-Object system.Windows.Forms.Button $btnChiudi.text = "Chiudi" $btnChiudi.width = 150 $btnChiudi.height = 67 $btnChiudi.location = New-Object System.Drawing.Point(628,298) $btnChiudi.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',16) $lblMessaggio = New-Object system.Windows.Forms.Label $lblMessaggio.text = "INSERIRE I DATI" $lblMessaggio.AutoSize = $true $lblMessaggio.width = 25 $lblMessaggio.height = 10 $lblMessaggio.location = New-Object System.Drawing.Point(40,493) $lblMessaggio.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10) $lblTemplate = New-Object system.Windows.Forms.Label $lblTemplate.text = "Modello Team utilizzato:" $lblTemplate.AutoSize = $true $lblTemplate.width = 25 $lblTemplate.height = 10 $lblTemplate.location = New-Object System.Drawing.Point(40,400) $lblTemplate.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',8) $txtTemplate = New-Object system.Windows.Forms.TextBox $txtTemplate.multiline = $false $txtTemplate.width = 405 $txtTemplate.height = 40 $txtTemplate.enabled = $true $txtTemplate.text = $DefaultModelTeam $txtTemplate.location = New-Object System.Drawing.Point(370,400) $txtTemplate.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',14) $lblTeamId = New-Object system.Windows.Forms.Label $lblTeamId.text = "" $lblTeamId.AutoSize = $true $lblTeamId.width = 25 $lblTeamId.height = 10 $lblTeamId.location = New-Object System.Drawing.Point(540,493) $lblTeamId.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10) $CorsoTeams.controls.AddRange(@($lblNomeCorso,$btnCrea,$lblDataCorso,$txtDataCorso,$txtNomeTeam,$btnChiudi,$lblMessaggio,$lblDescrizione,$txtDescrizione, $lblTeamId,$lblTemplate,$txtTemplate )) $txtDataCorso.text=Get-Date -Format "dd/MM/yyyy" $btnCrea.Add_Click({ $NomeTeamCompleto=$txtNomeTeam.text+" - "+$txtDataCorso.text CreaTeam $NomeTeamCompleto $txtDescrizione.text $lblMessaggio.text= "Team creato - TeamId:" }) $btnChiudi.Add_Click({$CorsoTeams.Close()}) [void]$CorsoTeams.ShowDialog()3Views0likes0CommentsAd-Hoc Entra MFA using SMS
Error : Get MFA Client Access TokenDone. Send MFA challenge to the user Done. OTP sent to your phone. Please enter the OTP: Enter the OTP sent via SMS: 696632 Invoke-RestMethod: C:\Git_Repo\MFA_Test\MFATestWIthKyle\sms.ps1:54:28 Line | 54 | … ionResult = Invoke-RestMethod -Uri 'https://strongauthenticationservi … | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | Service BODY { color: #000000; background-color: white; font-family: Verdana; margin-left: 0px; margin-top: 0px; | } #content { margin-left: 30px; font-size: .70em; padding-bottom: 2em; } A:link { color: #336699; font-weight: bold; | text-decoration: underline; } A:visited { color: #6699cc; font-weight: bold; text-decoration: underline; } A:active { color: | #336699; font-weight: bold; text-decoration: underline; } .heading1 { background-color: #003366; border-bottom: #336699 6px | solid; color: #ffffff; font-family: Tahoma; font-size: 26px; font-weight: normal;margin: 0em 0em 10px -20px; padding-bottom: | 8px; padding-left: 30px;padding-top: 16px;} pre { font-size:small; background-color: #e5e5cc; padding: 5px; font-family: | Courier New; margin-top: 0px; border: 1px #f0f0e0 solid; white-space: pre-wrap; white-space: -pre-wrap; word-wrap: break-word; | } table { border-collapse: collapse; border-spacing: 0px; font-family: Verdana;} table th { border-right: 2px white solid; | border-bottom: 2px white solid; font-weight: bold; background-color: #cecf9c;} table td { border-right: 2px white solid; | border-bottom: 2px white solid; background-color: #e5e5cc;} Service Endpoint not found. WARNING: Invalid OTP or validation failed. Below line causing the error $mfaValidationResult = Invoke-RestMethod -Uri 'https://strongauthenticationservice.auth.microsoft.com/StrongAuthenticationService.svc/Connector//ValidatePin' -Method POST -Headers $Headers -Body $XML -ContentType 'application/xml'18Views0likes0CommentsI want to create Sharepoint Verisoning Report on my Tenant
I have created a script, but dont seem to be able to find out how many file versions there are , Config Variables $TenantAdminURL = "https://admin.sharepoint.com" $CSVFilePath = "C:\Temp\RESTART.csv" #Get the Root Web #$Web = Get-PnpWeb #$versions = Get-SPOListItemVersion -ListItem $listItem #Get the Site Title Write-host -f Green $Web.Title #Connect to Admin Center using PnP Online Connect-PnPOnline -Url $TenantAdminURL -ClientId “cabf4-cc9b-4dcf-807b-8af94c3c4333" -Interactive -ForceAuthentication #Delete the Output Report, if exists if (Test-Path $CSVFilePath) { Remove-Item $CSVFilePath } #Get All Site collections - Exclude: Seach Center, Redirect site, Mysite Host, App Catalog, Content Type Hub, eDiscovery and Bot Sites $SiteCollections = Get-PnPTenantSite | Where { $.URL -like '/sites' -and $.Template -NotIn ("SRCHCEN#0", "REDIRECTSITE#0", "SPSMSITEHOST#0", "APPCATALOG#0", "POINTPUBLISHINGHUB#0", "EDISC#0", "STS#-1")} #Get All Large Lists from the Web - Exclude Hidden and certain lists $ExcludedLists = @("Form Templates","Site Assets", "Pages", "Site Pages", "Images", "Site Collection Documents", "Site Collection Images","Style Library") $SiteCounter = 1 #Loop through each site collection ForEach($Site in $SiteCollections) { #Display a Progress bar Write-Progress -id 1 -Activity "Processing Site Collections" -Status "Processing Site: $($Site.URL)' ($SiteCounter of $($SiteCollections.Count))" -PercentComplete (($SiteCounter / $SiteCollections.Count) * 100) #Connect to the site Connect-PnPOnline -Url $Site.URL -Interactive #Get all document libraries $DocumentLibraries = Get-PnPList | Where-Object {$_.BaseType -eq "DocumentLibrary" -and $_.Hidden -eq $False -and $_.Title -notin $ExcludedLists -and $_.ItemCount -gt 0} $ListCounter = 1 $ItemsColl = $List2.Items #Iterate through document libraries ForEach ($List in $DocumentLibraries) { $global:counter = 0 $FileData = @() Write-Progress -id 2 -ParentId 1 -Activity "Processing Document Libraries" -Status "Processing Document Library: $($List.Title)' ($ListCounter of $($DocumentLibraries.Count))" -PercentComplete (($ListCounter / $DocumentLibraries.Count) * 10) #Get All Files of the library with size > 100MB $Files = Get-PnPListItem -List $List -Fields FileLeafRef,FileRef,SMTotalFileStreamSize -PageSize 500 -ScriptBlock { Param($items) $global:counter += $items.Count; Write-Progress -Id 3 -parentId 2 -PercentComplete ($global:Counter / ($List.ItemCount) * 10) -Activity "Getting List Items of '$($List.Title)'" -Status "Processing Items $global:Counter to $($List.ItemCount)";} | Where {($_.FileSystemObjectType -eq "File") -and ($_.FieldValues.SMTotalFileStreamSize/1MB -gt 100)} #Collect data from each files ForEach ($File in $Files) { $FileData += [PSCustomObject][ordered]@{ Site = $Web.url Library = $List.Title FileName = $File.FieldValues.FileLeafRef URL = $File.FieldValues.FileRef Size = [math]::Round(($File.FieldValues.SMTotalFileStreamSize/1MB),2) } } #Export Files data to CSV File $FileData | Sort-object Size -Descending $FileData | Export-Csv -Path $CSVFilePath -NoTypeInformation -Append $ListCounter++ #Write-Progress -Activity "Completed Processing List $($List.Title)" -Completed -id 2 } $SiteCounter++ }14Views0likes0CommentsError PowerShell 30015-1015 (80)
Hello, using PowerShell for office installation, with ODT, it gives me the following error shown in the photo, or opening the console in any folder with the right mouse button "open the P.S. window here" gives an error: Missing termination character in the string: ". + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException + FullyQualifiedErrorId : TerminatorExpectedAtEndOfString Or Set-Location : Impossibile trovare un parametro posizionale che accetta l'argomento 'Ripristino\Office\Office'. In riga:1 car:1 + Set-Location -literalPath D:\Ripristino\File Ripristino\Office\Office ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidArgument: (:) [Set-Location], ParameterBindingException + FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.SetLocationCommand While if I run the command on the desktop, the window opens normally! Thanks21Views0likes0CommentsError PowerShell 300-1015 (80)
Hello, using P.Shell for office installation, with ODT, it gives me the following error shown in the photo, or opening the console in any folder with the right mouse button "open the P.S. window here" gives an error: Missing termination character in the string: ". + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException + FullyQualifiedErrorId : TerminatorExpectedAtEndOfString While if I run the command on the desktop, the window opens normally! Thanks15Views0likes0CommentsBest way to remove UseClientIntegration from each role definition (SharePoint Online)
I've created a PS script that removes Use Client integration from each permission level (role definition). This works, but as a side effect it gives the custom role definitions a new id. This can cause issues further down the line. Here is the part of the script which replaces the existing permission levels (role defs): #Install App to the Site Install-PnPApp -Identity $App.Id # Get all existing role definitions $roleDefinitions = Get-PnPRoleDefinition foreach ($role in $roleDefinitions) { # Create a new custom role definition by copying the existing one $newRoleName = "Custom_" + $role.Name # Clone the existing permission levels excluding Client Int.: Add-PnPRoleDefinition -RoleName $newRoleName -Clone $role -Exclude UseClientIntegration # Remove the original role definition Remove-PnPRoleDefinition -Identity $role.Name -Force } # Get the new role definitions: $newRoleDefinitions = Get-PnPRoleDefinition # Rename each permission to remove the "Custom_" foreach ($newRole in $newRoleDefinitions) { Set-PnPRoleDefinition -Identity $newRole.Name -NewRoleName $newRole.Name.TrimStart("Custom_") } # Remove the erroneously created permission levels: if($role.Name -eq "Custom_Limited Access" -or "Custom_Web-Only Limited Access" -or "Custom_Full Control") { Remove-PnPRoleDefinition -Identity "Custom_Limited Access" -Force Remove-PnPRoleDefinition -Identity "Custom_Web-Only Limited Access" -Force Remove-PnPRoleDefinition -Identity "Custom_Full Control" -Force Set-PnPRoleDefinition -Identity "ntribute" -NewRoleName "Contribute" #Not sure why earlier in the script it changes Contribute to "ntribute" but i'm having to rename it here. } I need a better way to do this, as you can see it's an amateur effort. I need someway to remove UserClientIntegration from each permission level but keep the original permission level role def id.27Views0likes0CommentsWebView2 HTML parsing
The code below embeds a web-scraping test URI into a Webview2 control. The button2 function returns the links for the site, but requires a redundant Invoke-WebRequest. I know Webview2 does not have an internal DOM capability and the $htmlContent = $WebView2.ExecuteScriptAsync() to return HTML is commented out as it does not appear to work. Is there a way to obtain the html without the Invoke-WebRequest()? function button1 { $title = 'Enter New URL to Navigate To' $msg = 'Please enter URL as https://[...some site...].com/.net/.org' $url = [Microsoft.VisualBasic.Interaction]::InputBox($msg, $title) $url if ($url -ne "") { try { $WebView2.Source = $url $WebView2.Refresh() } catch { $pop.popup("Invalid or Unuseable URL",2,"Error",4096) } } } function button2 { $links = ( Invoke-WebRequest -Uri $WebView2.Source).Links.Href | Get-Unique $regex = '/product*' $links | Select-String $regex | Select line | Out-Gridview -Title "Webpage Links for Products" -PassThru #$pop.popup("Code for Scraping Links",2,"Message",4096) } ######################################################################################################################## $pop = New-Object -ComObject wscript.shell New-Variable -Name 'data' -Value "$([Environment]::GetEnvironmentVariable('LOCALAPPDATA'))\Webview2" -Scope Global -Force $path=$PSScriptRoot # Get DLLs $WinForms = "$path\Microsoft.Web.WebView2.WinForms.dll" $Core = "$path\Microsoft.Web.WebView2.Core.dll" <# $loader = "$path\WebView2Loader.dll" $wpf = "$path\Microsoft.Web.WebView2.Wpf.dll" #> Add-Type -AssemblyName Microsoft.VisualBasic Add-Type -Path $WinForms #Add-Type -Path $Core Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing $Form = New-Object System.Windows.Forms.Form $button1 = New-Object System.Windows.Forms.Button $button2 = New-Object System.Windows.Forms.Button $cancelButton = New-Object System.Windows.Forms.Button # $button1.Location = New-Object System.Drawing.Point(23, 25) $button1.Name = "button1" $button1.Size = New-Object System.Drawing.Size(75, 23) $button1.TabIndex = 0 $button1.Text = "New URL" $button1.BackColor = "Green" $button1.ForeColor = "White" $button1.AutoSize = $true # $button2.Location = New-Object System.Drawing.Point(312, 25) $button2.Name = "button2" $button2.Size = New-Object System.Drawing.Size(75, 23) $button2.TabIndex = 1 $button2.Text = "Links" $button2.BackColor = "Green" $button2.ForeColor = "White" $button2.AutoSize = $true # $cancelButton.Location = New-Object System.Drawing.Point(684, 25) $cancelButton.Name = "button3" $cancelButton.Size = New-Object System.Drawing.Size(75, 23) $cancelButton.TabIndex = 2 $cancelButton.Text = "Close" $cancelButton.BackColor = "Red" $cancelButton.ForeColor = "White" $cancelButton.Text = 'Close Window' $cancelButton.AutoSize = $true $cancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel $Form.CancelButton = $cancelButton # $WebView2 = New-Object -TypeName Microsoft.Web.WebView2.WinForms.WebView2 $WebView2.CreationProperties = New-Object -TypeName 'Microsoft.Web.WebView2.WinForms.CoreWebView2CreationProperties' $WebView2.CreationProperties.UserDataFolder = $data #keeps it out of $PSScriptRoot $WebView2.Source = "https://www.scrapingcourse.com/ecommerce/" $Webview2.Location = New-Object System.Drawing.Point(23, 65) $Webview2.Size = New-Object System.Drawing.Size(749, 373) $WebView2.Anchor = 'top,right,bottom,left' <#navigation complete $WebView2_NavigationCompleted = { $htmlContent = $WebView2.ExecuteScriptAsync("window.chrome.webview.postMessage(document.documentElement.outerHTML;").Result #$htmlContent = $webView2.CoreWebView2.ExecuteScriptAsync("document.documentElement.outerHTML;").Result Write-Host $htmlContent } $WebView2.add_NavigationCompleted($WebView2_NavigationCompleted) $WebView2.add_WebMessageReceived({ param($WebView2, $message) $pop.popup($message.TryGetWebMessageAsString(),3,"Message",4096) }) #> $Form.ClientSize = New-Object System.Drawing.Size(800, 450) $Form.Controls.Add($Webview2) $Form.Controls.Add($cancelButton) $Form.Controls.Add($button2) $Form.Controls.Add($button1) $Form.Name = "Form" $Form.Text = "Webview Web Scraping Sample" $button1.add_click( { button1 }) $button2.add_click( { button2 }) $result=$Form.ShowDialog() #Terminate if Cancel button pressed if ($result -eq [System.Windows.Forms.DialogResult]::Cancel) { [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() $form.Dispose() Exit } ########################################################################################################################37Views0likes0CommentsAdding External Users in-bulk to: Microsoft Teams & Private Channel(s) within the Team
We have a customer who requires over 350 external users (their customers) to be added / invited into a Team which has been created. "Half" of the users need to be added into "private channel a", and the other "Half" need to be added into "private channel b". We have attempted to add the users via various PowerShell scripts, however none of these scripts that we have been provided with have worked for various reasons. I have been unable to locate any native methods for this within the MS 365 admin centre, therefore believe that the only way to achieve this is by PowerShell scripting. Example of the most recent script we have is as follows, omitting the creation of the private channel(s) as they have already been created - see below: We require assistance with the actual script itself to: Add users into the team from a CSV of their email addresses. Assign the users to the correct private channel. Note - users will be added in 2 batches - 1 per private channel, so we just require scripting that can be modified to achieve this. # Install the Microsoft Teams PowerShell Module Install-Module -Name PowerShellGet -Force -AllowClobber Install-Module -Name MicrosoftTeams -Force -AllowClobber # Connect to Microsoft Teams Connect-MicrosoftTeams # Define the team name and path to the CSV file $teamName = "Your Team Name" $csvPath = "C:\path\to\your\users.csv" # Get the GroupId of the team $team = Get-Team -DisplayName $teamName $groupId = $team.GroupId # Import users from the CSV file $users = Import-Csv $csvPath # Add external users to the team foreach ($user in $users) { Add-TeamUser -GroupId $groupId -User $user.Email } # Define the private channel name $privateChannelName = "Private Channel Name" # Create the private channel New-TeamChannel -GroupId $groupId -DisplayName $privateChannelName -MembershipType Private # Get the ChannelId of the private channel $channel = Get-TeamChannel -GroupId $groupId -DisplayName $privateChannelName $channelId = $channel.Id # Add users to the private channel foreach ($user in $users) { Add-TeamChannelUser -GroupId $groupId -User $user.Email -ChannelId $channelId }70Views0likes0CommentsAdd parent team in a shared channel with powershell
Hello, I'm trying to add the team, where my shared channel is, as a member oh this channel but I can't find the good command. The last one I used is : $TeamGrouId = "000-000-000-000" #ID de l'équipe concernée $Channel = "Mon canal partagé" $channelID = (Get-TeamChannel -GroupId $TeamGroupId | Where-Object { $_.DisplayName -Like $Channel}).Id $paramstest = @{ "@odata.type" = "microsoft.graph.aadUserConversationMember" roles = @("member") "*** Adresse électronique supprimée pour cause de confidentialité ***" = "https://graph.microsoft.com/v1.0/groups/$TeamGroupId" } Add-MgTeamChannelMember -TeamId $TeamGroupId -ChannelId $channelId -BodyParameter $paramstest The error message I'm encoutering is : Add-MgTeamChannelMember : Bind requests not expected for action payload. Status: 400 (BadRequest) ErrorCode: BadRequest If anyone have a solution :-) Best regards P.S. : I'm french so my english may be a little bad.24Views0likes0CommentsThreat Hunting with PowerShell - Security even with a small budget - there is no excuse!
Dear Threat Hunter, Lack of IT security is often excused by little or no available money. In my view, this is a very poor excuse. In this article I will try to give you a jump start on how to investigate threats with PowerShell. Is this a comprehensive and conclusive list of how you can find or detect threats/threats? NO, absolutely not. But it is meant to provide you with the support that you need to move forward on your own. Let's talk about the "general conditions": 1. If you use the PowerShell scripts I show/explain in this article, this is entirely your responsibility. I use the scripts in different situations, they are not dangerous, but you should already know what you are doing. 2. Written permission! If you are not sure if you are allowed to do an investigation, organize a written permission from your supervisor. 3. In the different scripts I sometimes (for this article deliberately) use standard search words like "malware", "malicious", "hacker" etc. Such search patterns/search words need to be customized, of course. These simply serve as an example. 4. The last part of the article examines some Microsoft cloud services. I am absolutely aware that there are a huge number of tools for hunting in the Microsoft cloud services. It starts with Azure Sentinel and continues with Cloud App Security. Since the focus is on a small budget, I'll leave those tools on the side. Introduction: So first, why should you use PowerShell for threat hunting? PowerShell is a useful threat hunting tool because it is a powerful scripting language and a platform for automating tools and accessing data across any Windows environment. It allows you to quickly gather information from various sources such as event logs, registries, files, and processes. Additionally, it can also be easily integrated with other tools and technologies making it a flexible and efficient tool for threat hunting. Some common use cases for PowerShell in the threat hunting environment include automated collection of log data, identification of unusual behavior anomalies in the system, the discovery of malware or malicious activity by known signatures or patterns or behaviors. These are just a few examples of how PowerShell can be used in a threat hunting capacity. Its versatility and ability to access and manipulate data from across the Windows environment make it a very valuable tool for any security professional. Threat Hunting in PowerShell - Use Cases: All right. So now that we understand where PowerShell can benefit an organization from a threat hunting perspective. Let's take a deeper look at some of the actual use cases you might encounter on a day to day basis, first being identify malicious processor files. So specifically, you can conduct raw file analysis to sift through different data shares to look for particular files in question whether that be a signature or even an extension of a certain file being able to quickly search and triage through files is an extreme benefit of using PowerShell for threat hunting. But how exactly do we start, what can we use as a guide? For example, the MITRE ATT&CK Framework. Here are a few examples: Indicator Removal: Clear Windows Event Logs https://attack.mitre.org/techniques/T1070/001/ Event Triggered Execution: Installer Packages https://attack.mitre.org/techniques/T1546/016/ Hide Artifacts: NTFS File Attributes https://attack.mitre.org/techniques/T1564/004/ Command and Scripting Interpreter: PowerShell https://attack.mitre.org/techniques/T1059/001/ Command and Scripting Interpreter: Windows Command Shell https://attack.mitre.org/techniques/T1059/003/ Event Triggered Execution: Windows Management Instrumentation Event Subscription https://attack.mitre.org/techniques/T1546/003/ Credentials from Password Stores: Windows Credential Manager https://attack.mitre.org/techniques/T1555/004/ Abuse Elevation Control Mechanism: Bypass User Account Control https://attack.mitre.org/techniques/T1548/002/ The MITRE ATT@CK framework provides a comprehensive and regularly updated overview of tactics, techniques, and procedures (TTPs) used by various threat actors. We can locate these TTPs using PowerShell, here are a few examples: Indicator Removal: Clear Windows Event Logs https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Hunting_Active_Directory/06_Account_Events.ps1 Event Triggered Execution: Installer Packages https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Tactics_Techniques_Procedures_(TTPs)/08_Get-ItemProperty_Software.ps1 Hide Artifacts: NTFS File Attributes https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Tactics_Techniques_Procedures_(TTPs)/08_Get-ItemProperty_Software.ps1 Windows Installer Service is running https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Tactics_Techniques_Procedures_(TTPs)/01_WIS_is_running.ps1 Search Alternate Data Streams on NTFS File Systems https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Tactics_Techniques_Procedures_(TTPs)/02_Search_ADS_on_NTFS%20_(specific%20file).ps1 https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Tactics_Techniques_Procedures_(TTPs)/03_Search_ADS_on_NTFS_file_systems.ps1 Read the Contents of a File https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Tactics_Techniques_Procedures_(TTPs)/06_Read_the_contents_file.ps1 Locating Data Patterns within a File https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Tactics_Techniques_Procedures_(TTPs)/05_locating_data_patterns_within_file.ps1 Search for Encoding with Regex https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Tactics_Techniques_Procedures_(TTPs)/07_Search_encoding_with_regex.ps1 Search for Command and Scripting Interpreter: https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Tactics_Techniques_Procedures_(TTPs)/04_Searching_for_PIDs.ps1 Threat hunting in different environments with PowerShell: Coming examples are about collecting information in very different environments. Also here a few examples as a kind => as first starting points: Hunt for Threats in Active Directory: https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Hunting_Active_Directory/01_Resetting_Password_Unlocking_Accounts.ps1 https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Hunting_Active_Directory/02_Search_stale_accounts.ps1 https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Hunting_Active_Directory/03_Users_without_Manager.ps1 https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Hunting_Active_Directory/04_Password_Expiration.ps1 https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Hunting_Active_Directory/05_Group_Membership_Report.ps1 https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Hunting_Active_Directory/06_Account_Events.ps1 https://github.com/tomwechsler/Active_Directory_Advanced_Threat_Hunting/blob/main/PowerShell/Tracking_the_Source_of_Account_Lock_Outs_and_Bad_Passwords.ps1 https://github.com/tomwechsler/Active_Directory_Advanced_Threat_Hunting/blob/main/PowerShell/Finding_Unused_Group_Policy_Objects.ps1 Some of the scripts are structured in such a way that they must be executed block by block/line by line. So do not execute the whole script at once. Pay attention to the different information that is collected. With some investigations in the Active Directory accounts can be indicated like "guest" or "krbtgt", there must be clear of course how this information is to be estimated. Depending on how and what information is searched. Hunt for Threats in Exchange Online: https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Hunting_Exchange_Online/Exchange_Mailbox_LastLogin.ps1 Find mailboxes with the last login. Hunt for Threats in Azure: https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Hunting_Azure/Collect_vms_subscription.ps1 We search Azure for all virtual machines in a subscription. https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Hunting_Azure/02_Graph_Create_Time_Last_Password.ps1 When was the last password change and when were the accounts created? Hunt for Threats in SharePoint: https://github.com/tomwechsler/Threat_Hunting_with_PowerShell/blob/main/Hunting_SharePoint_Online/SharePoint_Online_specific_files.ps1 With this script we search for files with the extension .ps1 in a SharePoint Online page. Summary: Is this the best tactic to hunt for threats? No! There are many different tactics/techniques to search for threats. First of all, there are a huge number of different tools that can be used, for example SIEM/SOAR (Security Information and Event Management/Security Orchestration, Automation and Response). These tools are really great, sometimes cost a lot and often it takes a lot of knowledge to use such tools. But what is the use of such tools if the information generated by these tools cannot be understood properly, not very much. For this reason, I have tried in this article with simple tools to generate information that hopefully can be interpreted. Is finished here at this point. NO, the journey continues. The examples in this article are neither exhaustive nor complete, but they should give you a starting point. I hope you can build on this foundation. I hope that this information is helpful to you and that you have received a good "little" foundation. But I still hope that this information is helpful for you. Thank you for taking the time to read the article. Happy Hunting, Tom Wechsler P.S. All scripts (#PowerShell, Azure CLI, #Terraform, #ARM) that I use can be found on github! https://github.com/tomwechslerGenerate vCard for each user in Exchange online and attach to user's mail.
We are looking best method to create vCard for each user in Exchange online and attach vCard in user's outlook for outgoing email. Is there any script or tool to programmatically create a vCard, that can generate .vcf files with the desired contact information for each user and integrate to Exchange online to apply to corresponding user.26Views0likes0CommentsHow to remove shared link from subfolder in sharepoint online?
I tried out remove shared link from subfolder by powershell in sharepoint online: Remove-PnPFolderSharingLink -Folder "/sites/site/subfolder/" -Identity 52 But I get error: Remove-PnPFolderSharingLink: Exception of type 'System.Management.Automation.PSInvalidOperationException' was thrown. And "Get-PnPException" shows: Message : Exception of type 'System.Management.Automation.PSInvalidOperationException' was thrown. Stacktrace : at PnP.PowerShell.Commands.Base.PnPConnectedCmdlet.ProcessRecord() in c:\build\src\Commands\Base\PnPConnectedCmdlet.cs:line 101 at PnP.PowerShell.Commands.PnPSharePointCmdlet.ProcessRecord() in c:\build\src\Commands\Base\PnPSharePointCmdlet.cs:line 121 at System.Management.Automation.CommandProcessor.ProcessRecord() ScriptLineNumber : 1 Is there a way to remove shared link from subfolder with powershell in sharepoint online?29Views0likes0CommentsHow does Defender work with other AV
Another thing I ran across that would like some explanation. Tested on both Win10/11. With both Avast and Defender checking AV with powershell Product GUID : {D68DDC3A-831F-4fae-9E44-DA132C1ACF46} Name : Windows Defender Real-time Protection Status : Enabled Computername : [ME] Product Executable : windowsdefender:// Reporting Exe : %ProgramFiles%\Windows Defender\MsMpeng.exe Definition Status : Up to date Product GUID : {EB19B86E-3998-C706-90EF-92B41EB091AF} Name : Avast Antivirus Real-time Protection Status : Enabled Computername : [ME] Product Executable : C:\Program Files\Avast Software\Avast\wsc_proxy.exe Reporting Exe : C:\Program Files\Avast Software\Avast\wsc_proxy.exe Definition Status : Up to date and if Defender is set up to scan on occasion shows up as enabled #when running # Get-MpComputerStatus AMEngineVersion : 1.1.24090.11 AMProductVersion : 4.18.24090.11 AMRunningMode : SxS Passive Mode AMServiceEnabled : True AMServiceVersion : 4.18.24090.11 AntispywareEnabled : True AntispywareSignatureAge : 21 AntispywareSignatureLastUpdated : 11/6/2024 5:05:45 AM AntispywareSignatureVersion : 1.421.135.0 AntivirusEnabled : True ...... more properties follow but not relevant here but when I try to update, get error #Update-MpSignature Update-MpSignature : Virus and spyware definitions update was completed with errors. At C:\PSFiles\AV\Defender.ps1:3 char:1 + Update-MpSignature + ~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (MSFT_MpSignature:ROOT\Microsoft\...SFT_MpSignature) [Update-MpSignature], CimException + FullyQualifiedErrorId : HRESULT 0x80070652,Update-MpSignature Is this an issue with Cim vs WMI?40Views0likes0Commentspowershell on mac
Hello Everyone, I’m new to using PowerShell on a Mac. I was given a PS1 script that needs to be run on a Mac device to check the files and folders of logged-in users. I managed to achieve this to some extent, but now I’m stuck on granting full disk access to the PWSH app via a Jamf Pro privacy payload. Since the PWSH app is not a signed package, I was unable to identify the “code requirements” to add a payload in Jamf Pro. Could you please share your best practices for deploying PowerShell and granting it full disk access via Jamf Pro?46Views0likes0CommentsAssistance Needed with OneDrive and Office 365 Activity PowerShell Scripts
Dear All, I am using the scripts below to retrieve OneDrive and Office 365 activity details for my tenant. However, in the "User Principal Name" field, I am getting an ID instead of the email address. This issue started after September 18; prior to that, it was working fine and showing the email addresses as expected. I run these scripts daily and generate a Power BI report based on the output. Scripts Used Get User OneDrive Activity: Invoke-GraphApiRequest -uri "https://graph.microsoft.com/v1.0/reports/getOneDriveActivityUserDetail(date=$yesterdaydate)" -FileName "OneDrive\ODUserDailyActivity\OneDriveUserDailyActivity" -Verbose:$VerbosePreference Invoke-GraphApiRequest -uri "https://graph.microsoft.com/v1.0/reports/getOneDriveUsageAccountDetail(date=$yesterdaydate)" -FileName "OneDrive\ODUsageDailyReport\OneDriveUsageDailyReport" -Verbose:$VerbosePreference Here is an example of the output I’m receiving: Report Refresh Date,User Principal Name,Is Deleted,Deleted Date,Last Activity Date,Viewed Or Edited File Count,Synced File Count,Shared Internally File Count,Shared Externally File Count,Assigned Products,Report Period 2024-11-22,830E1CDE8B16F21C80B207D213852737,False,,2024-11-22,1,0,0,0,MICROSOFT COPILOT STUDIO VIRAL TRIAL+MICROSOFT 365 E5+MICROSOFT COPILOT STUDIO USER LICENSE+MICROSOFT POWER AUTOMATE FREE+MICROSOFT INTUNE SUITE+MICROSOFT FABRIC (FREE)+MICROSOFT DEFENDER VULNERABILITY MANAGEMENT ADD-ON+DYNAMICS 365 CUSTOMER VOICE TRIAL+PLANNER AND PROJECT PLAN 3+POWER APPS PREMIUM+MICROSOFT POWER APPS FOR DEVELOPER,1 This is just one example, but I am seeing similar results for most of these scripts. Could you please help me resolve this issue? Thank you in advance!359Views0likes0CommentsHash/Array return values
I ran across a difference in return values for built in properties depending on whether collecting with a hash or an array. The code below selects an Excel document and iterates properties. If the $hash variable is set to $false the properties are collected as an array, otherwise as a hash. Script run in ISE for Powershell 5.1. A hash comes up with properties not included if array used. This is not a problem to be solved, per se, but is there an explanation for the difference. $hash = $true #change to $false for array $pop = New-Object -ComObject wscript.shell $FileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property @{ InitialDirectory = $PSScriptRoot Filter = 'Excel Files (*.xlsx;*.xls)|*.xlsx;*.xls' Title = 'Select Excel File' } $result = $FileBrowser.ShowDialog() If ($result -eq 'Cancel') { $popup = $pop.popup("No File Selected",2,"Closing",4096) exit } $xl = $FileBrowser.FileName $popup = $pop.popup("Iterating Built In Properties for $xl `n Please Wait",4,"Running",4096) $excel = New-Object -ComObject Excel.Application $excel.Visible = $false $workbook = $excel.Workbooks.Open($xl) $binding = "System.Reflection.BindingFlags" -as [type] if ($hash -eq $true) {$props = @{} } else {$props = @() } Foreach($property in $workbook.BuiltInDocumentProperties) { $pn = [System.__ComObject].invokemember("name",$binding::GetProperty,$null,$property,$null) trap [system.exception] { if ($hash -eq $true) { $props.Add($pn,"Value Not Found")} else {$props += "$pn`: Value Not Found"} continue } if ($hash -eq $true) { $props.Add($pn,[System.__ComObject].invokemember("value",$binding::GetProperty,$null,$property,$null)) } else {$props += "$pn`:" + [System.__ComObject].invokemember("value",$binding::GetProperty,$null,$property,$null) } } $excel.quit() $excel = $null $props | FT [gc]::collect() [gc]::WaitForPendingFinalizers() Exit36Views0likes0CommentsM365 Exchange & Shared Mailbox Calendar Notifications
M365 Business Premium tenant here. We have a shared mailbox (not resource mailbox) called meetings@ The aim is to have an anonymous email address staff can send out meeting requests to our clients and have a dedicated calandar for those meetings. I've set up the staff members with Receive and SendAs permissions so everyone can set up meetings using that account so that the meeting invite appears to come from meetings@ instead of the staff members email address. Staff can create meetings and the invite is anonymous so that part is working as planned. The problem is, all the staff gets flooded with Accept/Decline messages going to their personal mailboxes. Is there a way to set it so that only the shared mailbox gets the notifications or even supress those messages entirely. I've resorted to email rules for each staff member to either block or divert the messages but I'd really prefer it if there was a one stop shop rahter than having to configure individual's mailboxes to block them. I tried Set-CalendarProcessing -Identity "email address removed for privacy reasons" -RemoveForwardedMeetingNotifications $true -AutomateProcessing AutoUpdate but it didn't seem to do much. Any other ideas?39Views0likes0CommentsThe Script Fails to Obtain Modified Date of the files in SPO library
Hello, I used a script (see attachment) but failed to obtain the correct modified date of the files in the SPO library. For example; The "Modified" column displays the date of the last file version, while the "$File.TimeLastModified" script retrieves the date of file activities, including non-version-changing actions like moving the file or discarding checkouts. How can I retrieve the exact dates of the files shown in the SP library "Modified" column? Regards,154Views0likes0CommentsSPFx Application Customizer not registering when using PowerShell JSON to register
I've been tasked to create a site script which will run when an SPO global admin creates an SPO Site collection: $themeName = "ASCv12" $site_script = @' { "$schema": "schema.json", "actions": [ { "verb": "setSiteExternalSharingCapability", "capability": "Disabled" }, { "verb": "applyTheme", "themeName": "Blue Yonder" }, { "verb": "associateExtension", "title": "site-script-hide-client-side-solution", "location": "ClientSideExtension.ApplicationCustomizer", "clientSideComponentId": "34e874b4-48ed-4fa6-97b9-04a6054f2902", "scope": "Site" } ], "bindata": { }, "version": 1 } '@@ I've read this documentation: Site template JSON schema | Microsoft Learn which shows the exact syntax I'm using. I've tried using the clientSideComponentId from the: package-solution.json manifest.json Root App catalog Product Id number (I've uploaded it to this app catalog.) But when the site is created, the extension does not work. I've checked logging and nothing is triggering. The SPFx extension is an Application Customizer which is vanilla. I've not customized it because I wanted to test it out first of all. But as mentioned, it's not triggering. Can anyone advise? Has anyone encountered this before and did you resolve it?103Views0likes0Comments
Events
Recent Blogs
- One new resource, logging improvements and bugfixes. This is what SharePointDsc v5.2 is bringing to the table!May 12, 20227.5KViews1like0Comments
- 2 MIN READThis article describes a solution of an issue I have been troubleshooting today, where switching to an AllSigned execution policy resulted in the "This publisher is explicitly not trusted on your sys...Mar 18, 20226.4KViews1like1Comment