Retrieving Credentials from the Secure Store Service

I hate that once you set the credentials in a Secure Store Service, you can’t see what they are in the GUI. So this script from Bob Guidinger is a fabulous solution. Thanks Bob! (I would have left a comment on your site, but it wouldn’t let me for some reason 😦 )

$context = Get-SPServiceContext -Site HTTPS://WEBAPP1

$provider = New-Object Microsoft.Office.SecureStoreService.Server.SecureStoreProvider
$provider.Context = $context

$marshal = [System.Runtime.InteropServices.Marshal]

try
{
    $apps = $provider.GetTargetApplications()
    foreach ($app in $apps)
    {
       Write-Output "`n$($app.Name)"
        Write-Output "$('-'*80)"
        try
        {
            $creds = $provider.GetCredentials($app.Name)
            foreach ($cred in $creds)
            {
                $ptr = $marshal::SecureStringToBSTR($cred.Credential)
                $str = $marshal::PtrToStringBSTR($ptr)

                Write-Output "$($cred.CredentialType): $($str)"
            }
        }
        catch
        {
            Write-Output "Error getting credentials!"
        }
        Write-Output "$('-'*80)"
    }
}
catch
{
    Write-Output "Error getting Target Applications."
}

$marshal::ZeroFreeBSTR($ptr)

Replace-RequestAccessEmail

Retrieve and update/change access request on all the sites/webs inside a web application that has my email as the access request. Some sites have a branch site managers email group that need to be used as well. Sourced from PowerShell script to update Access Request Email Address for multiple sites and webs and Windows PowerShell: Build a Better Function.

function Replace-RequestAccessEmail {
<#
.SYNOPSIS
Update Access Request containing Specified Email
.DESCRIPTION
Find all the Access Request Settings that have a specified email and replace it with either another account, another account and the Branch Site Managers email, or remove from string
.EXAMPLE
Replace the email address with a new address and the Branch Site Managers email
Replace-RequestAccessEmail -webapp "https://WEBAPP1" -email "Joe.Cool@email" -replace "SPADMIN@email" -SPSiteMgrs
.EXAMPLE
Removes the email address from the string
Replace-RequestAccessEmail -webapp "https://WEBAPP1" -email "Joe.Cool@email"
.PARAMETER webapp
Web Application to cycle through.
.PARAMETER email
Email in Access Request Setting to locate
.PARAMETER replace
(OPTIONAL) Email to use as replacement - leave off to have email removed from string.
.PARAMETER SPSiteMgrs
(OPTIONAL) Include to replace using Site Managers email based on Site Collection
#>
[CmdletBinding()]
param
(
[Microsoft.SharePoint.PowerShell.SPWebApplicationPipeBind]$webapp,
[string]$email,
[string]$replace,
[switch]$SPSiteMgrs
)
process {
$webapplication = Get-SPWebApplication -Identity $webapp
[string]$trimSites = $webapplication.URL
$trimSites = $trimSites + "sites/"
foreach($site in $webapplication.Sites)
{
foreach($web in $site.AllWebs)
{
if ($web.HasUniquePerm -and $web.RequestAccessEnabled)
{
if ($web.RequestAccessEmail -like "*$email*")
{
$siteCollection = $web.Site | %{$_.URL}
$siteCollection = $siteCollection.TrimStart($trimSites)
Write-Host "Site Collection: " $siteCollection
Write-Host "On Web" $web.Title ", URL" $web.URL
Write-Host "`tAccess requests go to :" $web.RequestAccessEmail
If($SPSiteMgrs.IsPresent)
{
If(!$replace.IsPresent)
{
#Default with Admin Account
$replace = "SPADMIN@EMAIL"
}
switch ($siteCollection)
{
"BRANCH_A" {$requestAccessEmail = "$replace; BRANCH_A.SPSiteMgr@EMAIL"}
"BRANCH_B" {$requestAccessEmail = "$replace; BRANCH_B.SPSiteMgr@EMAIL"}
"BRANCH_C" {$requestAccessEmail = "$replace; BRANCH_C.SPSiteMgr@EMAIL"}
default {$requestAccessEmail = "$replace"}
}
} else
{
$requestAccessEmail = $web.RequestAccessEmail
$reqeustAccessEmail = $requestAccessEmail.ToLower()
$email = $email.ToLower()
$requestAccessEmail = $requestAccessEmail.Replace($email,$replace)
}
# If the Request Access Email would be blank, set to SharePoint Admins Email
If ($requestAccessEmail -eq "")
{
$requestAccessEmail = "SPADMIN@EMAIL"
}
Write-Host "`t***Will Update Request Access Email to: "$requestAccessEmail "***"
$web.RequestAccessEmail = $requestAccessEmail
$web.Update()
}
}
} #end ForEachWeb
#Dispose of the site object
$site.Dispose()
} #end ForEachSite
} #end Process
} #endFunction

Get AD Group Member Users

Script pulls data from a group on the Exchange Domain and looks up user information in the Users domain. User name and email address are output to CSV file.

# -----------------------------------------------------------------------------
# Script: Get-ADGroupMemberUsers.ps1
# Author: Heather Van De Sande
# Date: 01/08/2013
# Keywords: PowerShell, Active Directory
# Comments: Recursively pull users from an Exchange Group and retrieve User information
#            from User Domain
# -----------------------------------------------------------------------------
Import-module ActiveDirectory
# Useful for finding the server information for below variables
#Get-ADDomain -Identity "exch.corp.xx.com"
#Get-ADDomain -Identity "user.corp.xx.com" 
$strExchangeServer = "EXCHSV-DC1.EXCH.CORP.XX.COM"
$strUserServer = "USERSV-DC1.USER.CORP.XX.COM"
$strOutPutCSVFile = "C:\Temp\HR_All_Staff.csv"
$strParentGroupName = "HR All Staff"

$strParentGroupID = Get-ADGroup -Filter {Name -eq $strParentGroupName} -Server $strExchangeServer | Select DistinguishedName
$strMembers = Get-ADGroupMember -Identity $strParentGroupID.DistinguishedName -Server $strExchangeServer -Recursive
$strUsers = @()
foreach ($strMember in $strMembers){
    $strUsers += Get-ADUser -Identity $strMember.DistinguishedName -Server $strUserServer -Properties DisplayName,EmailAddress | Select DisplayName,EmailAddress
}
$strUsers | Export-Csv -Path $strOutPutCSVFile -NoTypeInformation

Copy Users between SharePoint Groups

I had a situation where I needed to create a new site and copy over specific groups from the old site. The groups had already been created using another script. Thanks to Justin Kobel (the smartest person I personally know) for helping me with this script.

$allGroups = (“Eastern Mountain Contributors”, “Eastern Mountain Visitors”, “Salt River Contributors”, “Salt River Visitors”)
$fromWeb = ‘https://WEBAPP/sites/SITEA&#8217;
$toWeb = ‘https://WEBAPP/sites/SITEB&#8217;
foreach ($group in $allGroups){
Write-Host “Group: $group”
$users = Get-SPUser -Web $fromWeb -Group $group | Select-Object LoginName
foreach ($user in $users){
New-SPUser –UserAlias $user.LoginName –web $toWeb
Set-SPUser -Identity $user.LoginName -Group $group -Web $toWeb
}
}

Get all Users in AD Group

UPDATE: Expanded script can be found here, where I needed to do this with the group being in the exchange domain and user information in a separate domain.

Recently I had a request to list all users to a particular site – due to the unique nature of the site and the provisioning of those user accounts, those users are all members of an AD group (the AD group is then added to a SharePoint Group.)

Thanks to “Query for User Accounts in Active Directory with PowerShell” I was able to get onto the right track.

“Import-module ActiveDirectory” provides cmdlets that let you manage Active Directory domains.

I first pulled up all the information for a user I knew belonged to that group.

Get-ADUser -Filter {Surname -eq “Lastname”} -SearchBase ” DC=AD,DC=COMPANY,DC=COM ” -properties *

Looking at the MemberOf property, I was able to get the entire identity I needed for the Get-ADGroupMember cmdlet.


I created this script to retrieve the Display Name and Email Address for all members of the “Site_SP_Visitors” Group. List is then piped to a CSV file.

Get-ADGroupMember -Identity “CN=
Site_SP_Visitors,OU=Groups,OU=ZYG,OU=LKJS,OU=XYZ_Users,DC=AD,DC=COMPANY,DC=COM” | Get-ADUser -Properties DisplayName,EmailAddress | Select DisplayName,EmailAddress | Export-CSV “C:\temp\
Site_SP_Visitors.csv”

No two choices should have the same ID

Add from existing site columns results in error. Looking up the Correlation ID in the ULS Logs gave a clue of “System.ArgumentException: No two choices should have the same ID.” You know, Microsoft, it would help to know what ID that was. But I guess that would deny me the fun of looking for it. I tried the advice given by Lars Lynch, but the SharePoint Manager didn’t allow me to sort the field’s information, so didn’t really help me.

Using PowerShell, I got a listing of all the fields, their ID’s and their Internal Names:

$web = Get-SPWeb https://webapp/sites/SITE/WEB 
foreach ($field in $web.Fields){ 
 write-output "$($field.Group) | $($field.Title) | $($field.Id) | $($field.InternalName)" 
}
$web.Dispose() 

This gave me a list I could sort in excel (after some manipulation) to try and find duplicates. Unfortunately that didn’t work. So I starting comparing the list it gave me to the one on the site columns page (_layouts/mngfield.aspx).

There I discovered a field listed twice on the site columns page, but only once in my list (note: the list output shown is the unsorted view):

After ensuring the field wasn’t used anywhere, I modified my script to remove the field:

$findGUID = "75af1e7d-ad81-4566-9edd-a582b2a7de13" 
$web = Get-SPWeb https://webapp/sites/SITE/WEB 
foreach ($field in $web.Fields){ 
 if($field.Id -eq $findGUID){
 write-output "$($field.Group) | $($field.Title) | $($field.Id) | $($field.InternalName)"
 $field.Allowdeletion = $true
 $field.Sealed = $false
 $field.Delete()
 Break
 }
}
$web.Dispose() 

The two instances of the field were removed from the site and I was able to “Add from existing site columns” again without error.

Update Permission Set

I needed to create a script to update two permission sets throughout my farm:

$allWebApps = ("https://WEBAPP1.COM", "https://WEBAPP2.COM")
function fnUpdatePermSet {
    #Get the root Web site of the site collection.
    $web=$site.RootWeb
    # Use the RoleDefinitions property of the SPWeb class to get the collection of role definitions for a Web site. Use an indexer to return a single item from the collection.
    $UpdatePermissionSet=$web.RoleDefinitions[$roleName];
    # Use the BasePermissions property of the SPRoleDefinition class to set the base permissions for a role definition.
    $UpdatePermissionSet.BasePermissions=$rolePerms;
    # Use the Update Method of the SPRoleDefinition class
    $UpdatePermissionSet.Update()
}
foreach ($siteUrl in $allWebApps){
   Write-Host "$siteUrl";
   $rootSite = New-Object Microsoft.SharePoint.SPSite($siteUrl);
   $spWebApp = $rootSite.WebApplication;
   foreach($SPSite in $spWebApp.Sites){
      #Get site collection
      $site = Get-SPSite $SPSite
      Write-Host $site
      $roleName = "Edit-Add";
      $rolePerms = "ViewListItems,AddListItems,EditListItems,OpenItems,ViewVersions,ViewFormPages,Open,ViewPages,BrowseDirectories,BrowseUserInfo,UseRemoteAPIs,UseClientIntegration,CreateAlerts,ManagePersonalViews";
      fnUpdatePermSet;
      
      $roleName = "Add Only";
      $rolePerms = "ViewListItems,AddListItems,OpenItems,ViewVersions,ViewFormPages,Open,ViewPages,BrowseDirectories,BrowseUserInfo,UseRemoteAPIs,UseClientIntegration,CreateAlerts,ManagePersonalViews";
      fnUpdatePermSet;

      #Dispose of the site object
      $site.Dispose()
   }
$rootSite.Dispose();
}

Customize the Access Denied Page

Part of the reason we want to change the Access Request page is the users get confused when they see “Error”. We want them to “knock on the door of sites” and request access so the email will go to the user-site manager of the site. They see – and focus on – “Error”, ignore the tiny “Request Access” link, and call the help desk saying they can’t get to SharePoint. There is no error; this is the process we want them to use. Our Access Denied page needs to be more user-friendly.

This took much research and was a little scary for me, as it involved delving into Visual Studio instead of just using the SharePoint Designer. I was able to piece it together thanks to Vijai Anand (How to create custom SharePoint 2010 Application Page using Visual Studio 2010) and Soderlind (SharePoint 2010: Customize out of the box Application Pages (AccessDenied, Confirmation, Error, Login, RequestAccess, Signout, WebDeleted )) and a co-worker who helped with the CSS portion. This was developed on a Development Server (that had Visual Studio 2010 installed and SharePoint installed) and then deployed to our Test Server using PowerShell (the same will be used to go to Production.) I’m including all of the steps for the benefit of admins who are not developers.

Step 1: Make the page using Visual Studio.

File > New > Project

Empty SharePoint Project. (We use Team Foundation Server for our Source Control, so the part about “Add to Source Control” may not apply to you.)

Connect to your Development SharePoint application. Select “Deploy as a farm solution”.

Right-click the project, Add > New Item

Select Application Page and name your new page.

Now, because we want to retain the ability to “Request Access” and “Sign in as different user” and those parts are looking for pages relative to the called page, I’m going to move the ExampleAccessDenied.aspx out of the folder where it got created and delete the folder. This is because, if I don’t, the deployment will create a folder in the layouts directory and mess up my relative path.


Step 2: Modify the page

I want most of the functionality provided by the default Access Denied page so I’m going to copy and paste all of the code out of the OTB page into my own. Find the AccessDenied.aspx in the hive. (Typically: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS)

Once you’ve replaced all the code in your page with the copied code, you can build and deploy your solution for a look at your progress. Right-click and build your solution.

Then right-click and deploy your solution. This will put a copy of the application page into the layouts folder of your web application.

You should be able to browse to your application page. It looks just like the Access Denied page.

Back to Visual Studio and the code on the page. I’m going with the assumption that you’re not a developer and most of this looks like Sanskrit. I’m going to show you section by section what was there and what I put so hopefully, if you want to make simple changes similar to mine, you’ll be able to figure it out. All the code, before and after, is included here: ExampleAccessDenied

PlaceHolderPageTitle

This is what puts the Error: Access Denied at the top of the Explorer bar and the tab window.

We want to replace the word Error with our company initials.

BTW, you can comment out sections by selecting the code and hitting the icon next to the tab-in tab-out icons. It puts the <%– marks around the commented code –%>

PlaceHolderPageTitleInTitleArea

This is the other Error: Access Denied we wanted to get rid of.

A friendlier You do not have access to this resource.

PlaceHolderPageImage

The very intimidating X; that’s got to go.

We’re just going to comment that section out altogether.

PlaceHolderAdditionalPageHead

This is where my co-worker helped me. We wanted to control the formatting of the section and needed a little brute force to get the error image to stop showing. Sorry I don’t have more commentary for this section, but hopefully you’ll be able to use what we did.

PlaceHolderMain

This is the main meat and potatoes section. We removed the icon, made a friendlier list of options, and moved the user info to the bottom. I’ve included the entire thing in a pdf you can download. ExampleAccessDenied

Build and deploy the solution again.

Package your solution

Change the configuration from Debug to Release.

Right-click and package the solution.

The output will show you where it put the wsp file. This is the file you will need to deploy to our other environments (because you DO follow an SDLC process right?)

Step 3: Tell SharePoint to use the new page

We do NOT want to just rename our page to AccessDenied.aspx.

cls $snapIn = Get-PSSnapin | where-object {$_.Name -eq "Microsoft.SharePoint.PowerShell"}
if($snapIn -eq $null) { 
 Add-PsSnapin Microsoft.SharePoint.PowerShell 
} 
Set-SPCustomLayoutsPage -Identity "AccessDenied" -RelativePath "/_layouts/ExampleAccessDenied.aspx" -WebApplication "http://DEVWEBAPPLICATION" 
Get-SPCustomLayoutsPage –Identity "AccessDenied" -WebApplication "http://DEVWEBAPPLICATION " 
iisreset

Step 4: Deploy

In the new environment, put the wsp file into a temporary holding place. We used a folder called solutions. Then run the powershell to deploy the solution and Set SPCustomLayoutsPage. I have the powershell saved in a pdf for that as well (DeployCustomAccessDeniedPowershell.)

Set the variables at the top for the solution name, release folder name, and web applications you need to release to:

$allSolutions = (“CustomAccessDenied.wsp”)
$releaseFolderName = “C:\Solutions\CustomABCD\”
$allURLs = (“https://WEBAPP1&#8221;,https://WEBAPP2)

At the very bottom, you’ll need to change the aspx page as well:

“/_layouts/CustomAccessDenied.aspx”

Voila!

ExampleAccessDenied
DeployCustomAccessDeniedPowerShell

Set Incremental Crawl through PowerShell

Thanks to the HabaÑero Blog (awesome name for a blog) I was able to change my incremental search crawl through PowerShell.

Set-SPEnterpriseSearchCrawlContentSource -identity CRAWLSOURCE -SearchApplication “Search Service Application” -ScheduleType Incremental -DailyCrawlSchedule -CrawlScheduleRunEveryInterval 1 -CrawlScheduleStartDateTime 06:00 -CrawlScheduleRepeatInterval 15 -CrawlScheduleRepeatDuration 720 -Confirm:$false

Update RequestAccessEmail with PowerShell

Update: I have an improved version of this at:  Replace-RequestAccessEmail

I sometimes forget to change the Request Access Email from using my account to the group admin distribution email. This helps correct that:

$webapp = Get-SPWebApplication "https://WEBAPP"
foreach($site in $webapp.Sites){
     foreach($web in $site.AllWebs){
         if ($web.HasUniqueRoleDefinitions){
             if($web.RequestAccessEnabled){
                 Write-Host $Web.URL
                 Write-Host $Web.RequestAccessEmail
                 Write-Host "---"
                 if ($web.RequestAccessEmail -eq 'my.email@email.com'){
                     Write-Host $Web.URL "Update RequestAccessEmail"
                     Write-Host "---"
                     $web.RequestAccessEmail ="SPADMIN.GROUP@email.com"
                     $web.Update()
                 }
             }
             else {
                 Write-Host "Access Request Settings not enabled." $Web.URL
             }
         }
     }
 }