Create Multiple SPGroups in all the subsites if the SPGroups doesn't exist using power Shell script - sharepoint

I need to create 2 SPGroups in the SubSites with Multiple AD Group members added to the group.
For Example,
I have sub sites x,y,z under my Site
I have groups Group A, Group B
Members of Group A are Member1, Member2, Member3
Members of Group B are Member1, Member2, Member3, Member4, Member5
I need to create the groups in the sub sites if they doesn't exist.
Please let me know for any better approach.
I am trying to do that using Power shell.
$SPWeb = $Web.Read()
if ($SPWeb.SiteGroups[$GroupName] -ne $null){
throw "Group $GroupName already exists!"
}
if ($SPWeb.Site.WebApplication.UseClaimsAuthentication){
$op = New-SPClaimsPrincipal $OwnerName -IdentityType WindowsSamAccountName
$mp = New-SPClaimsPrincipal $MemberName -IdentityType WindowsSamAccountName
$owner = $SPWeb | Get-SPUser $op
$member = $SPWeb | Get-SPUser $mp
}
else {
$owner = $SPWeb | Get-SPUser $OwnerName
$member = $SPWeb | Get-SPUser $MemberName
}
$SPWeb.SiteGroups.Add($GroupName, $owner, $member, $Description)
$SPGroup = $SPWeb.SiteGroups[$GroupName]
$SPWeb.RoleAssignments.Add($SPGroup)
$SPWeb.Dispose()

#Create an XML with all the details for the new Groups and menbers for the groups
<?xml version="1.0"?>
<Groups>
<Group name="GroupA" description="Test GroupA" PermissionLevel="Contribute">
<Users>
<User>Domain\Member1</User>
<User>Domain\Member2</User>
<User>Domain\Member3</User>
</Users>
</Group>
<Group name="GroupB" description="Test GroupB" PermissionLevel="Read">
<Users>
<User>Domain\Member1</User>
<User>Domain\Member2</User>
<User>Domain\Member3</User>
<User>Domain\Member4</User>
<User>Domain\Member5</User>
</Users>
</Group>
#Now create a script file with the below script
#Get Site and Web objects
$site = get-spsite https://portal/site/Test/
#Get all the sub sites under the web
foreach($web in $site.allwebs)
{
#Get XML file containing groups and associated users
$groupsXML = [xml] (Get-Content ("C:\Temp\Groups.XML"))
#Walk through each group node defined in the XML file
$groupsXML.Groups.Group | ForEach-Object {
[string]$permissionLevel = $_.PermissionLevel
#Check to see if SharePoint group already exists in the site collection
if ($web.SiteGroups[$_.name] -eq $null)
{
#If the SharePoint group doesn't exist already - create it from the name and description values at the node
$web.SiteGroups.Add($_.name, $web.CurrentUser, $null, $_.description)
$newGroup = $web.SiteGroups[$_.name]
}
#Get SharePoint group from the site collection
$group = $web.SiteGroups[$_.name]
#Add the users defined in the XML to the SharePoint group
$_.Users.User | ForEach-Object {
$group.AddUser($_, "", "", "")
}
$roleAssignment = new-object Microsoft.SharePoint.SPRoleAssignment($group)
$roleDefinition = $web.Site.RootWeb.RoleDefinitions[$permissionLevel]
$roleAssignment.RoleDefinitionBindings.Add($roleDefinition)
$web.RoleAssignments.Add($roleAssignment)
Write-Host "Group Name: "$group" Created at site: " $web.url
}
#Dispose of Web and Site objects
$web.Dispose()
$site.Dispose()
}

Related

How do I find the set of Site Pages to which I have assigned unique permissions?

I am the owner of a SharePoint Online site, and I have a handful of pages that I wanted to hide from Visitors, so I went to the Site Pages folder and clicked Manage Access for each file I wanted to hide, and I clicked the name of the SiteVisitors group and clicked Stop Sharing. The result is that Visitors cannot navigate or even see those pages in a Search. Now I want to restore the access for Visitors.
BUT, let's say I forgot the names of the pages that I hid. I can still see the pages because I am the Owner, but I forgot which pages the Visitors cannot see. So how do I generate a list of all pages that have non-standard permissions?
You can get all pages with unqiue permissions in your Site Pages library using below CSOM powershell.
#Load SharePoint CSOM Assemblies
Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server
Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll"
Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server
Extensions\16\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
#Function to call a non-generic method Load
Function Invoke-LoadMethod() {
param([Microsoft.SharePoint.Client.ClientObject]$Object = $(throw "Please provide a Client Object"),[string]$PropertyName)
$ctx = $Object.Context
$load = [Microsoft.SharePoint.Client.ClientContext].GetMethod("Load")
$type = $Object.GetType()
$clientLoad = $load.MakeGenericMethod($type)
$Parameter = [System.Linq.Expressions.Expression]::Parameter(($type), $type.Name)
$Expression = [System.Linq.Expressions.Expression]::Lambda([System.Linq.Expressions.Expression]::Convert([System.Linq.Expressions.Expression]::PropertyOrField($Parameter,$PropertyName),[System.Object] ), $($Parameter))
$ExpressionArray = [System.Array]::CreateInstance($Expression.GetType(), 1)
$ExpressionArray.SetValue($Expression, 0)
$clientLoad.Invoke($ctx,#($Object,$ExpressionArray))
}
#Define Parameter values
$SiteURL="https://crescent.sharepoint.com/sites/PMO"
$ListName="Site Pages"
Try {
#Setup Credentials to connect
$Cred= Get-Credential
$Credentials = New-Object
Microsoft.SharePoint.Client.SharePointOnlineCredentials($Cred.Username, $Cred.Password)
#Setup the context
$Ctx = New-Object Microsoft.SharePoint.Client.ClientContext($SiteURL)
$Ctx.Credentials = $Credentials
#Get All Lists of the web
$List = $Ctx.Web.Lists.GetByTitle($ListName)
$Ctx.Load($List)
$Ctx.ExecuteQuery()
Write-host "Total List Items Found:"$List.ItemCount
#Query to Get 2000 items from the list
$Query = New-Object Microsoft.SharePoint.Client.CamlQuery
$Query.ViewXml = "<View Scope='RecursiveAll'><RowLimit>2000</RowLimit></View>"
#Batch process list items - to mitigate list threshold issue on larger lists
Do {
$ListItems = $List.GetItems($Query)
$Ctx.Load($ListItems)
$Ctx.ExecuteQuery()
$Query.ListItemCollectionPosition = $ListItems.ListItemCollectionPosition
#Loop through each List item
ForEach($ListItem in $ListItems)
{
Invoke-LoadMethod -Object $ListItem -PropertyName "HasUniqueRoleAssignments"
$Ctx.ExecuteQuery()
if ($ListItem.HasUniqueRoleAssignments -eq $true)
{
Write-Host -f Green "List Item '$($ListItem["Title"])' with ID '$($ListItem.ID)' has Unique Permissions"
}
else
{
Write-Host -f Yellow "List Item '$($ListItem["Title"])' with ID '$($ListItem.ID)' is inhering Permissions from the Parent"
}
}
} While ($Query.ListItemCollectionPosition -ne $null)
}
Catch {
write-host -f Red "Error Checking Unique Permissions!" $_.Exception.Message
}
Or use PnP PowerShell.
#Set Variables
$SiteURL = "https://crescent.sharepoint.com/sites/PMO"
$ListName = "Site Pages"
#Connect to PnP Online
Connect-PnPOnline -Url $SiteURL -UseWebLogin
#Get all list items in batches
$ListItems = Get-PnPListItem -List $ListName -PageSize 2000
#Iterate through each list item
ForEach($ListItem in $ListItems)
{
#Check if the Item has unique permissions
$HasUniquePermissions = Get-PnPProperty -ClientObject $ListItem -Property
"HasUniqueRoleAssignments"
If($HasUniquePermissions)
{
Write-Host -f Green "List Item '$($ListItem["Title"])' with ID '$($ListItem.ID)' has Unique Permissions"
}
Else
{
Write-Host -f Yellow "List Item '$($ListItem["Title"])' with ID '$($ListItem.ID)' is inhering Permissions from its Parent"
}
}
Reference: SharePoint Online: Get All List Items with Unique Permissions using PowerShell
You can also delete all unique permissions in a library using PowerShell. Reference: SharePoint Online: Delete Unique Permissions for All Items in a List using PowerShell

Running a powershell script using multiple threads

I'm trying to execute a powershell script from within another powershell script. My current script runs fine, but I'd like to try and speed it up if possible.
What my script does is import a list of contacts into Each users Contacts folder through EWS.
In my powershell script the script that handles the importing I call it like this (ImportContacts is a Function without any arguments):
. $rootPath\ImportContacts\ImportContacts.ps1
ImportContacts
When I run it normally, as I mentioned above everything works fine, I just would like to speed it up. I tried following some examples of implementing runspacepool in Powershell to take advantage of using multiple threads, but it doesn't seem to be working properly for me. I'm sure it's a silly syntax error, but I have this currently:
Measure-Command{
$MaxThreads = 5
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads)
$RunspacePool.Open()
$Jobs = #()
ImportContacts | Foreach-Object {
$PowerShell = [powershell]::Create()
$PowerShell.RunspacePool = $RunspacePool
$PowerShell.AddScript({ImportContacts})
$Jobs += $PowerShell.BeginInvoke()
}
while ($Jobs.IsCompleted -contains $false)
{
Start-Sleep 1
}}
This seems to do the job, but I can't tell a difference with the speed.
EDIT 5-15-21
To better assist with the question, here is how I retrieve the user data. I have a file I call called "ExportedContacts.ps1"
It's called like so:
$Users = & $rootPath\ExportContacts\ExportContacts.ps1
The contents of the file is this
$Users = Get-ADUser -Filter * -Properties extensionAttribute2, middlename, mobile, OfficePhone, GivenName, Surname, DisplayName, EmailAddress, Title, Company, Department, thumbnailPhoto | Where-Object {($_.extensionAttribute2 -like "DynamicDistro") -AND (($_.Mobile -ne $NULL) -OR ($_.OfficePhone -ne $NULL))} | Select-Object #{Name="First Name";Expression={$_.GivenName}},#{Name="Last Name";Expression={$_.Surname}},#{Name="Display Name";Expression={$_.DisplayName}},#{Name="Job Title";Expression={$_.Title}},#{Name="Company";Expression={$_.Company}},#{Name="Department";Expression={$_.Department}},#{Name="Mobile Phone";Expression={$_.Mobile}},#{Name="Home Phone";Expression={$_.OfficePhone}}, #{Name="Middle Name";Expression={$_.MiddleName}}, #{Name="E-mail Address";Expression={$_.EmailAddress}}, thumbnailPhoto | Sort-Object "Last Name"
return $Users
Then I import those contacts, similarly to how I mentioned above. The content of the import is as follows:
Function ImportContacts
{
Write-Host "Importing Contacts. This can take several minutes."
#******************************************************************
foreach ($ContactItem in $Users)
{
$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
$ExchangeContact = New-Object Microsoft.Exchange.WebServices.Data.Contact($service);
$ExchangeContact.NickName = ('{0} {1}' -f $ContactItem."First Name", $ContactItem."Last Name"). Trim()
$ExchangeContact.DisplayName = $ExchangeContact.NickName;
$ExchangeContact.FileAs = $ExchangeContact.NickName;
$ExchangeContact.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1] = $ContactItem."E-mail Address";
$ExchangeContact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::HomePhone] = $ContactItem."Home Phone";
$ExchangeContact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::MobilePhone] = $ContactItem."Mobile Phone";
$ExchangeContact.Department = $ContactItem."Department";
$ExchangeContact.CompanyName = $ContactItem."Company";
$ExchangeContact.JobTitle = $ContactItem."Job Title";
$ExchangeContact.MiddleName = $ContactItem."Middle Name";
# Save the contact
$ExchangeContact.Save($ContactsFolder.Id);
}
}
I am also including the File that creates the contacts folder I specify and also deletes existing contacts (if the folder already exists), so that each import is a clean and updated import. I'd be curious if there is a faster way to clean the existing items?
Function CreateContactsFolder
{
Write-Host "Creating Contacts Folder and Cleaning up stale items. This can take a couple minutes."
Try
{
$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
$RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
$RootFolder.Load()
#Check to see if they have a contacts folder that we want
$FolderView = new-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
$ContactsFolderSearch = $RootFolder.FindFolders($FolderView) | Where-Object {$_.DisplayName -eq $FolderName}
if($ContactsFolderSearch)
{
$ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind($service,$ContactsFolderSearch.Id);
#If folder exists, connect to it. Clear existing Contacts, and reupload new (UPDATED) Contact Info
Write-Host "Folder alreads exists. We will remove all contacts under this folder."
# Attempt to empty the target folder up to 10 times.
$tries = 0
$max_tries = 0
while ($tries -lt 2)
{
try
{
$tries++
$ErrorActionPreference='Stop'
$ContactsFolder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete, $true)
$tries++
}
catch
{
$ErrorActionPreference='SilentlyContinue'
$rnd = Get-Random -Minimum 1 -Maximum 10
Start-Sleep -Seconds $rnd
$tries = $tries - 1
$max_tries++
if ($max_tries -gt 100)
{
Write-Host "Error; Cannot empty the target folder; `t$EmailAddress"
}
}
}
}
else
{
#Contact Folder doesn't exist. Let's create it
try
{
Write-Host "Creating new Contacts Folder called $FolderName"
$ContactsFolder = New-Object Microsoft.Exchange.WebServices.Data.ContactsFolder($service);
$ContactsFolder.DisplayName = $FolderName
$ContactsFolder.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
}
catch
{
Write-Host "Error; Cannot create the target folder; `t$EmailAddress"
}
}
return $ContactsFolder
}
Catch
{
Write-Host "Couldn't connect to the user's mailbox. Make sure the admin account you're using to connect to has App Impersonization permissions"
Write-Host "Check this link for more info: https://help.bittitan.com/hc/en-us/articles/115008098447-The-account-does-not-have-permission-to-impersonate-the-requested-user"
}
}

UserLastLogon -Export

Hi I'm trying to export a list of AD users based on "Last Logon"
I've scripted using base powershell however I'd be interested if anyone can find a solution using "AzureAD to Powershell" commands.
I've gotten as far as getting the list however I cannot export it to any file type because of how it generates through the loop.
End result I'm looking for is to be able to organize the data to see which users have been inactive?
Import-Module ActiveDirectory
function Get-ADUserLastLogon([string]$userName) {
$dcs = Get-ADDomainController -Filter {Name -like "*"}
$time = 0
foreach($dc in $dcs) {
$hostname = $dc.HostName
$user = Get-ADUser $userName | Get-ADObject -Properties lastLogon
if($user.LastLogon -gt $time) {
$time = $user.LastLogon
}
}
$dt = [DateTime]::FromFileTime($time)
Write-Host $username "last logged on at:" $dt
}
$unames = Get-ADUser -Filter 'ObjectClass -eq "User"' | Select -Expand SamAccountName
foreach ($uname in $unames) { Get-ADUserLastLogon($uname); }
In Azure AD, we can get all user Sign-ins records on Azure Portal or using Azure AD PowerShell.
If you are looking for a way by PowerShell to export Azure AD users last login list with user account status (enabled or not), just try the code below:
Connect-AzureAD
$AllUsers = Get-AzureADUser -All $true
$AllSiginLogs = Get-AzureADAuditSignInLogs -All $true
$results = #()
foreach($user in $AllUsers){
$LoginRecord = $AllSiginLogs | Where-Object{ $_.UserId -eq $user.ObjectId } | Sort-Object CreatedDateTime -Descending
if($LoginRecord.Count -gt 0){
$lastLogin = $LoginRecord[0].CreatedDateTime
}else{
$lastLogin = 'no login record'
}
$item = #{
userUPN=$user.UserPrincipalName
userDisplayName = $user.DisplayName
lastLogin = $lastLogin
accountEnabled = $user.AccountEnabled
}
$results += New-Object PSObject -Property $item
}
$results | export-csv -Path d:\result.csv -NoTypeInformation
export to .csv file Result:
There is one thing that you should know, for different Azure AD service tier, the time that Azure AD keep these data is different, details see here.

Change multiple files content type programatically using CSOM for SharePoint Online

I am attempting to change all the files in a library from one content type to another. This is in SharePoint Online so I'm using the CSOM. I am new to this so I'm stuck at where to go from what I have below.
I think my biggest issue is getting the values of the content types. That and I'm so used to SP On Premise I'm having trouble grasping this CSOM stuff. Much thanks to anyone that can help point me in the right direction!
#Load necessary module to connect to SPOService
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime") | Out-Null
#Login Information for script
$User = "user"
$Pass = "password"
$WebUrl = "SiteURL"
#Connect to SharePoint Online service
Write-Host "Logging into SharePoint online service." -ForegroundColor Green
$Context = New-Object Microsoft.SharePoint.Client.ClientContext($WebUrl)
$Context.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($User, (ConvertTo-SecureString $Pass -AsPlainText -Force))
#Get the Necessary List
Write-Host "Getting the required list." -ForegroundColor Green
$List = $Context.Web.Lists.GetByTitle("TestLibrary")
Write-Host "Getting the Content Types." -ForegroundColor Green
$oldCT = $list.ContentTypes("OldCTName")
$newCT = $list.ContentTypes("NewCTName")
$newCTID = $newCT.ID
$Query = [Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery(1000);
$Items = $List.GetItems($Query);
$Context.Load($Items);
$Context.ExecuteQuery();
#Check if the values specified for the content types actually exist on the list
if (($oldCT -ne $null) -and ($newCT -ne $null))
{
#Go through each item in the list
#Edit existing list items
foreach($item in $Items)
{
#Check if the item content type currently equals the old content type specified
if ($_.ContentType.Name -eq $oldCT.Name)
{
#Check the check out status of the file
if ($_.File.CheckOutType -eq "None")
{
#Change the content type association for the item
$_.File.CheckOut()
write-host "Resetting content type for file" $_.Name "from" $oldCT.Name "to" $newCT.Name
$_["ContentTypeId"] = $newCTID
$_.Update()
$_.File.CheckIn("Content type changed to " + $newCT.Name, 1)
}
else
{
write-host "File" $_.Name "is checked out to" $_.File.CheckedOutByUser.ToString() "and cannot be modified"
}
}
else
{
write-host "File" $_.Name "is associated with the content type" $_.ContentType.Name "and shall not be modified"
}
}
}
else
{
write-host "One of the content types specified has not been attached to the list"$list.Title
}
$Context.ExecuteQuery();
}

How to update SharePoint enterprise keywords with multiple values using powershell?

I'm trying to write a powershell script that looks at a user defined variable and updates the enterprise keywords for each item in a list that matches that value.
For instance, say you have a page in SP that has the managed metadata keywords: new, fresh, clean
I want a script that asks a user what keyword they want to swap out. So a user would specify a variable as: fresh and another variable as: fresher and it would update any item with the keyword fresh to fresher.
Here's what I 've used before but doesn't work now because there are mutliple values:
Add-PSSnapin Microsoft.SharePoint.PowerShell -EA SilentlyContinue
$webURL = <MY SP URL>
$listName = <MY LIST NAME>
$web = Get-SPWeb $webURL
$list = $web.Lists[$listName]
$listitems = $list.items
$session = Get-SPTaxonomySession -Site $web.Site
$termStore = $session.TermStores["Managed Metadata Service"]
$group = $termStore.Groups["Resources"]
$termset = $group.TermSets["Wiki Categories"]
$terms = $termSet.GetTerms(100)
$wiki1 = read-host "Enter the wiki category you want to update"
$wiki2 = read-host "Enter the replacement wiki category"
$term = $terms | ?{$_.name -eq $wiki2}
Foreach($item in $listitems)
{$wiki = $item["Wiki Categories"]
if($wiki.label -eq $term)
{
$spitem = [Microsoft.SharePoint.SPListItem]$item;
$taxfield = [Microsoft.SharePoint.Taxonomy.TaxonomyField]$spitem.Fields["Wiki Categories"]
$taxfield.SetFieldValue($spitem, $term)
$spitem.Update()
$spitem.File.Publish("True")
}
}
I'm pretty sure the issue is with this line:
$term = $terms | ?{$_.name -eq $wiki2}
And this line:
$taxfield.SetFieldValue($spitem, $term)
The problem is that you passing a TermCollection ($term) to SetFieldValue where you should pass in a TaxonomyFieldValueCollection.
You can convert them like this:
$taxfield = $spitem.Fields["Wiki Categories"]
$tfvc = new-object -typename Microsoft.SharePoint.Taxonomy.TaxonomyFieldValueCollection -argumentlist $taxfield;
foreach($t in $ term)
{
$tfv = new-object -typename Microsoft.SharePoint.Taxonomy.TaxonomyFieldValue -argumentlist $taxfield
$tfv.TermGuid = $t.Id
$tfv.Label = $t.Name
$tfvc.Add($tfv)
}
...
$taxfield.SetFieldValue($spitem, $tfvc)

Resources