August 12, 2013

Get Users Last Logon Time and Date using PowerShell

A question we sometimes need, but can't get from SharePoint is users last logon time. Usually you have an environment where a user signs in to the network and is authorized to access the company intranet without further password requirements in a single sign on environment. But the information isn't stored in SharePoint, so we can't get it from there.

However, the information is stored in Active Directory, and by importing it, you can get the information when all of your users where last active (logon) on your domain.

# Load the SharePoint cmdlets
$snapin = Get-PSSnapin | Where-Object {$_.Name -eq 'Microsoft.SharePoint.Powershell'} 
if ($snapin -eq $null
{    
    Write-Host "Loading SharePoint Powershell Snapin"    
    Add-PSSnapin "Microsoft.SharePoint.Powershell"  -EA SilentlyContinue
}

# Import ActiveDirectory cmdlets
Import-Module ActiveDirectory

# Here's the function that will return the last logon date and time
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 
}

# Get the user profiles
$site = Get-SPSite "https://intranet.company.com/"
$context = Get-SPServiceContext $site
$profileManager = New-Object Microsoft.Office.Server.UserProfiles.UserProfileManager($context
$profiles = $profileManager.GetEnumerator()

# Iterate all profiles and grab the users last logon date time and write to console
foreach($user in $profiles)
{
     Get-ADUserLastLogon -UserName $user["UserName"]
}

Reference: Determining a User's Last Logon Time

August 7, 2013

Make metadata column containing multiple terms readable with PowerShell

Sometimes you find yourself in a situation where you need to read the metadata column, which contains multiple values. These values are sometimes hard to read, and contain a lot of extra information (that you may or may not need) that needs to be stripped, to make the report readable. The string may look like this:

10;#Process|c6a2e0b3-0ac7-41d3-a0d4-bfca9d113c2f;#35;#Report|ea772779-c5ca-4992-a338-ff686479032f;#31;#Policy|523d8c9b-23b7-4746-96f2-a497f37012fb

But what you really want is Process;Report;Policy which is stripped from the column value. Starting in the beginning lets add the PSSnapin to our PowerShell if it’s not loaded.

$snapin = Get-PSSnapin | Where-Object {$_.Name -eq 'Microsoft.SharePoint.Powershell'}
if ($snapin -eq $null)
{   
    Write-Host "Loading SharePoint Powershell Snapin"   
    Add-PSSnapin "Microsoft.SharePoint.Powershell"  -EA SilentlyContinue
}

Hook up to your web and iterate yourself down to the metadata column.

$url = "https://intranet.company.com/document-center/"
$web = Get-SPWeb $url

foreach($list in $web.Lists)
{
    if($list.BaseType -eq "DocumentLibrary")
    {
        foreach($item in $list.items)
        {
            $file = $item.File

Having an instance of the file, makes us check whether or not the metadata column has any value. If it has any value, let’s clean it from the ugly output

            # Subject
            if($file.Properties["Subject"] -ne "")
            {
                $Subject = $file.Properties["Subject"]
               
                if($Subject -like '*#*')
                {

At first, we need to find out how many occurrence’s we have of the hash-character through the following script

                    $SubjectSplit = ""
                    $char = "#"
                    $result = 0..($Subject.length -1) | ? {$Subject[$_] -eq $char}

Calling $result.count will give us the number of times “#” is used in the string. When we know that, we can iterate over the string and pull the information we need. We’re separating terms by semi colon “;” which unfortunately leaves one extra “;” in the end

                    for($i=1; $i -le $result.count;$i=$i+2)
                    {
                        $SubjectSplit = $SubjectSplit + $Subject.split("#")[$i].split("|")[0] + ";"
                    }

But it’s quite easy to remove

                    $Subject = $SubjectSplit.TrimEnd(";")

And finally write the output to the host, so you know you got it right before putting the information in a csv-file.

                }
            }
            Write-Host $Subject
        }
    }
}
$web.Dispose();

The complete script:

$snapin = Get-PSSnapin | Where-Object {$_.Name -eq 'Microsoft.SharePoint.Powershell'}
if ($snapin -eq $null)
{   
    Write-Host "Loading SharePoint Powershell Snapin"   
    Add-PSSnapin "Microsoft.SharePoint.Powershell"  -EA SilentlyContinue
}
$url = "https://intranet.company.com/document-center/"
$web = Get-SPWeb $url

foreach($list in $web.Lists)
{
    if($list.BaseType -eq "DocumentLibrary")
    {
        foreach($item in $list.items)
        {
            $file = $item.File

            # Subject
            if($file.Properties["Subject"] -ne "")
            {
                $Subject = $file.Properties["Subject"]
               
                if($Subject -like '*#*')
                {
                    $SubjectSplit = ""
                    $char = "#"
                    $result = 0..($Subject.length -1) | ? {$Subject[$_] -eq $char}
                    for($i=1; $i -le $result.count;$i=$i+2)
                    {
                        $SubjectSplit = $SubjectSplit + $Subject.split("#")[$i].split("|")[0] + ";"
                    }
                    $Subject = $SubjectSplit.TrimEnd(";")
                }
            }
            Write-Host $Subject
        }
    }
}
$web.Dispose();


June 25, 2013

Add user permission to a all webs/sites if missing.

Sometimes you find yourself in a situation where you need to give specific permission to users on all sites and webs in your farm. To avoid duplicates, first check if the user has got permission on the site/web before adding another line of user to your permission list.

$contentWebAppServices = (Get-SPFarm).services |
? {$_.typename -eq "Microsoft SharePoint Foundation Web Application"}

foreach($webApp in $contentWebAppServices.WebApplications)
{
    Write-Host "Web Application  : " $webApp.name

    foreach ($site in $webApp.Sites)
    {
        Write-Host "  " $site.url -foregroundcolor "yellow"
   
foreach ($web in $site.AllWebs)
        {
           Write-Host "    " $web.title -foregroundcolor "magenta"
            $permission = Get-SPUser -Web $web.url -Limit All | select UserLogin, @{name="Exlicit given roles";expression={$_.Roles}}, @{name="Roles given via groups";expression={$_.Groups | %{$_.Roles}}},Groups | Where-Object {$_.UserLogin -like "domain\user"}
           if ($permission -notlike "Full Control")
           {
Write-Host "     User hasn't got permission." -foregroundcolor "red"
                Set-SPUser -Identity 'domain\user' -Web $web.url -AddPermissionLevel "Full Control"
           }
           else
           {
               Write-Host "     User has got permission." -foregroundcolor "green"
           }
        }
        $site.dispose()
    }

May 24, 2013

UnGhosting Customized Page Layouts with PowerShell


Sometimes you end up in a solution where someone have been kind enough to use SharePoint Designer to edit a Page Layout. This means that you are not able to change the Page Layout through your deployed code, in a package. Changes made in the development environment doesn’t show up in production. Your Page Layout is in a Ghosted mode of its origin, and we need to Un-Ghost the Page Layout to be able to change it from a package created in our development environment.

Thus you use this script to first find out which Page Layouts have been changed, and uncomment the Revert Content Stream to actually change the Page Layout back to an UnGhosted mode.

$s = Get-SPSite https://siteCollectionUrl
$w = $s.RootWeb

$ps = New-Object Microsoft.SharePoint.Publishing.PublishingSite($s)
$pls = $ps.PageLayouts
    
foreach ($pl in $pls)
{        
    $f = $w.GetFile($pl.ServerRelativeUrl)

    if ($f.CustomizedPageStatus -eq "Customized")
    {
        Write-Host
        Write-Host "Layout page name: " -NoNewline
        Write-Host $f.Name        
        Write-Host "Status before: " -NoNewline

        Write-Host $f.CustomizedPageStatus
         
        #$f.RevertContentStream()
      
        Write-Host "Status after: " -NoNewline
        Write-Host $f.CustomizedPageStatus
    }
}
    
$w.Dispose()
$s.Dispose()

April 10, 2013

SharePoint 2010 PowerShell: List all Web Applications deployed solutions


When you need to know which wsp solution is deployed to which Web Application the following script can be of use. First, get all your web applications and iterate through them. While in the loop print web application name and application pool name. Last, iterate all solutions and print their names - like this:

# Get all installed solutions per web application

$contentWebAppServices = (Get-SPFarm).services |
? {$_.typename -eq "Microsoft SharePoint Foundation Web Application"}

foreach($webApp in $contentWebAppServices.WebApplications)
{
    Write-Host "Web Application  : " $webApp.name
    Write-Host "Application Pool : " $webApp.ApplicationPool.Name
    Get-SPSolution | ForEach-Object {
        if ($_.LastOperationDetails.IndexOf($webApp.url) -gt 0)
        {
            Write-Host "    Solutions:"
            Write-Host "   " $_.DisplayName
        }
    }


The output looks like this: 

Web Application  : SharePoint - 1337
Application Pool : SharePoint - 1337
Web Application  : SharePoint - 80
Application Pool : SharePoint - 80

    Solutions:
    customer.intranet.wsp 


The first web application (SharePoint - 1337) doesn't have deployed solutions, but the second (SharePoint - 80) has one.

March 8, 2013

SharePoint 2010: Iterate all webs in Web Application

Sometimes you're just there and need to do something with all of your SPWebs, but have the unfortunate structure of having http://Portal/Sites/SiteCollections in your architecture. There is a way out which is quite simple and working very well. All you need to do is to get hold of all the SiteCollections (SPSite) in the WebApplication (http://portal).


$SPWebApp = Get-SPWebApplication "http://portal/"

foreach ($SPSite in $SPWebApp.Sites)
{
    if ($SPSite -ne $null)
    {
        foreach ($SPWeb in $SPSite.AllWebs)
        {
            Write-Host $SPWeb.Title

            if ($SPWeb -ne $null)
            {
                $SPWeb.Dispose()
            }
        }
    }

    if ($SPSite -ne $null)
    {
        $SPSite.Dispose()
    }
}

You also have the benefit of not missing a web inside an entire Web Application.

February 19, 2013

SharePoint 2010: Migrate Users to new Active Directory Environment

When switching from one AD-environment to another, you sometimes end up with users who don't get imported correct. You see them when they don't have first name and last name when they are logged in to SharePoint. They have <domain>\<username> visible instead.

But let's start from the beginning and migrate the users with the following command:


STSADM –o migrateuser –oldlogin OLDDOMAIN\user1 –newlogin
    NEWDOMAIN\user1 –ignoresidhistory

After that you need to run a full user profile synchronization. When this is done, continue and see which users where not imported correct.



# Clean up accounts that is not imported correctly

$upsa = Get-SPServiceApplication | Where-Object {$_.TypeName -like
    "User Profile Service Application"}

# List all user accounts that is not imported correctly
Set-SPProfileServiceApplication $upsa -GetNonImportedObjects $true

# Remove user accounts not imported correctly 
# Uncomment line below to run
Set-SPProfileServiceApplication $upsa -PurgeNonImportedObjects $true

# Run a full User Profile Service Syncronisation, and make sure users
# end up in Profile database.
# If not, users who log in will create new NonImportedObject accounts

This might have to be repeated several times, especially if you're in a live environment where users login during the day. Eventually these orphan user profiles will disappear.

If you have a lot of users, the script will take a lot of time - so please be patient unless you get an error from PowerShell.


Good Luck!

Reference: SharePoint 2010–User Information Lists and User Profile Cleanup