Showing posts with label PowerShell. Show all posts
Showing posts with label PowerShell. Show all posts

Tuesday, August 27, 2013

Adjusting Office 365 licenses in PowerShell

So, we just got hit with Wave 15 finally. This in and of itself was actually something of an accident - Microsoft only allows you to specify one "early adopter" group for testing Office 365 updates, and my boss elected only himself. Further, he realized about three days before our update that Microsoft offers a small notice at the bottom of the upgrade announcement email that you must notify them within 7 days if you wish to postpone your update.

So, we got upgraded in a bit of a hurry.

Now that we're on Wave 15, we have the new Outlook Web Access, which features new topbar buttons for Newsfeed, Sites, and SkyDrive. Well, we're using on-premise SharePoint and have no plans to use SharePoint Online, so those buttons are just going to confuse our user community. It turns out that in order to hide these buttons, you actually have to disable the SharePoint Online (and Office Web Apps) license options for each user that you want to conceal them from.

I was pretty certain I could do this with PowerShell, and it turns out I was right, but not without some gotchas involved. Here's what the final script looks like:

$e1Sku = Get-MsolAccountSku | ? {$_.AccountSkuId -match "STANDARDPACK"}
$e2Sku = Get-MsolAccountSku | ? {$_.AccountSkuId -match "STANDARDWOFFPACK"}
$e3Sku = Get-MsolAccountSku | ? {$_.AccountSkuId -match "ENTERPRISEPACK"}

$e1LicenseOption = New-MsolLicenseOptions -AccountSkuId $e1Sku.AccountSkuId -DisabledPlans SHAREPOINTSTANDARD
$e2LicenseOption = New-MsolLicenseOptions -AccountSkuId $e2Sku.AccountSkuId -DisabledPlans SHAREPOINTSTANDARD, SHAREPOINTWAC

$users = Get-MsolUser | ? {$_.IsLicensed -eq $true}

foreach($user in $users)
{
    foreach($license in $user.Licenses)
    {
        switch($license.AccountSkuId)
        {
            "company:STANDARDPACK"
            {
                Write-Output $user.DisplayName
                $user | Set-MsolUserLicense -RemoveLicenses $e1sku.AccountSkuId -AddLicenses $e2sku.AccountSkuId
                $user | Set-MsolUserLicense -RemoveLicenses $e2sku.AccountSkuId -AddLicenses $e1sku.AccountSkuId -LicenseOptions $e1LicenseOption
                break;
            }
            "company:STANDARDWOFFPACK"
            {
                Write-Output $user.DisplayName
                $user | Set-MsolUserLicense -RemoveLicenses $e2sku.AccountSkuId -AddLicenses $e3sku.AccountSkuId
                $user | Set-MsolUserLicense -RemoveLicenses $e3sku.AccountSkuId -AddLicenses $e2sku.AccountSkuId -LicenseOptions $e2LicenseOption
                break;
            }
        }
    }
}

My boss freaked out seeing this, knowing that removing an Exchange Online license meant the mailbox gets trashed. We spent close to an hour between illustrative tests and dashed alternatives before I finally convinced him that this was safe, and the only option. I'm rather surprised not to see a Set-MsolUserLicenseOptions cmdlet, and simply running Set-MsolUserLicense -LicenseOptions does nothing. It's accepted as valid input, so maybe the cmdlet isn't fully functional. I dunno.

Anyway, you'll notice that I run Set-MsolUserLicense twice. According to MSDN documentation, you can run -RemoveLicenses and -AddLicenses in the same cmdlet and both actions will occur simultaneously, meaning that a user will not be left without a license for any period of time. I tested this on a few accounts and had no issue myself. However, since I want to keep the same license level but merely change the product options, I tried to use the same AccountSkuId in both -AddLicenses and -RemoveLicenses, and it did not work. What I ended up having to do instead was to briefly elevate the user's license level and then re-add the old license with the -LicenseOptions as I had configured them. Without doing this, the cmdlet would always fail insisting that a provided AccountSku was not valid.

Thursday, December 20, 2012

Dealing with orphaned user solutions

I'm surprised this problem isn't more common. It seems easy enough to trigger by accident, and fixing it was a puzzle.

No doubt most of us have at some point in using SharePoint 2010 created a site template, and then after gazing upon it in the Solution Gallery, determined that we've named it wrong, need to change one small thing, or any number of different issues. Normally, the correct thing to do in this circumstance is to deactivate and then delete the solution.

What if you don't deactivate it?

Create a site.
Save it as a template to the Solution Gallery.
Download the WSP to your desktop.
Delete the solution from the Solution Gallery *without* deactivating it. To do this, click the Edit button and then click Delete from there.
Upload the WSP back to the Solution Gallery.

You'll find that you're not able to activate the WSP you just uploaded, or deactivate it. And now you're just kinda stuck with a site template that's visible when you try to create a new site, but nothing that can be done about it!

So, first off, user solution WSPs still contain features. We *can* deactivate the feature that installs this site template (via PowerShell).

$site = Get-SPSite http://my.site.com
$site.Features | Where-Object {$_.FeatureDefinitionScope -eq "Site"}
Disable-SPFeature $feature -url http://my.site.com

But that's only hiding the problem, it's not actually fixing it. So, we should try to identify the user solution and remove it instead.

Uninstall-SPUserSolution -Identity "BasicProjectSite.wsp" -Site http://my.site.com

But this tells me that there is no solution with that name. But when I list $site.Solutions, it's there. Even running Get-SPUserSolution lists 0 solutions. What to do? Wait, does the object model still work?

Name                           SolutionId                           Status
----                           ----------                           ------
BasicProjectSite.wsp           af9fc924-26b1-4c4f-9250-57b26c81316f Activated

$solution = $site.Solutions[[guid]"af9fc924-26b1-4c4f-9250-57b26c81316f"]
$site.Solutions.Remove($solution)

And it's finally gone!

Wednesday, December 5, 2012

CredSSP across forests (except not)

Sorry, this post isn't going to give any insightful gleaning into how this works. I'm making a guess that it could work, but I would have to get a bunch of people to dedicate time and cycles to making it work, and only a small number of said individuals would likely think it important enough to actually do.

We're in the process of domain consolidation, and the domain my office is moving to is admin'ed by different folks. Our farm is not moving, we're opting instead to keep it in its current domain and simply stand up 2013 in the new domain and (to channel some Xzibit here) "migrate while we migrate". There's trust issues (from a certificate standpoint, not human trust) meaning that anytime I try to RDP into my own servers, Windows complains that they're not trusted anymore (don't recall getting those warnings before when I was in my old domain trying to remote into servers in the current domain, so I'm guessing their GPO doesn't have our certificate server as trusted, or some other AD nonsense). Accordingly, it's fouled up my credential delegation configuration (or perhaps this is also GPO at work).

At any rate, I have actual work to do and don't have time just yet to suss this out...so...saying goodbye to PowerShell remoting for now...

Wednesday, October 10, 2012

PowerShell 3 and SP2010, forever apart...?

It's well-documented that SP2010 is so hardcore about .Net 3.5 that it has a tattoo on its biceps of the build number in an XML element.

Of course, in my eagerness to adopt PowerShell and start to flex my muscles with it (which admittedly is rather tough to do when you don't have a sizeable farm to manage), I deployed PowerShell 3 on my development server, and now suddenly PowerShell doesn't like me anymore.

There's a small handful of posts out there (mostly related to PowerGUI) that essentially tell the story: PowerShell v3 doesn't play well with the SP2010 cmdlets (because they won't work with .Net 4.0). I tried to use a .config file for the PowerShell executeable to deny it access to .Net 4.0, but that simply didn't work as the PS engine requires 4.0. The common recommendation is to change the shortcuts for PowerShell on the upgraded servers to add "-version 2" to the launch command for PowerShell.

Well, that's great and all, but what about PS Remoting? Whenever I launch a local PS client and then remote into a server, the remote system is still running v3 (see $psversiontable), and I can't very well invoke a new instance of PowerShell from that remote session by trying the "powershell -version 2" trick from a remote session.

Luckily, PSv3 helps us out somewhat. First, we need to create a PSSessionConfigurationFile on the remote server:

New-PSSessionConfigurationFile -Path .\PowerShellv2.pssc

Now, open that file, and look for the line which contains the following:

# PowerShellVersion =

Uncomment this line and add in the target version like so:

PowerShellVersion = '2.0'

Save your changes, and now run:

Register-PSSessionConfiguration -Name Microsoft.PowerShellv2 -Path .\PowerShellv2.pssc

The configuration name is up to you, I used it simply because there's an existing configuration entry for PSv3 named "Microsoft.PowerShell". You'll get a couple of confirmation prompts and the WinRM service will be restarted (make sure you're not kicking anyone out of the system!). Once this is done, from your client:

Enter-PSSession -ComputerName MyServer -Authentication CredSSP -Credential $credentials -ConfigurationName Microsoft.PowerShellv2

Now just to check:

$psversiontable


Name                           Value
----                           -----
CLRVersion                     2.0.50727.5456
BuildVersion                   6.1.7601.17514
PSVersion                      2.0
WSManStackVersion              2.0
PSCompatibleVersions           {1.0, 2.0}
SerializationVersion           1.1.0.1
PSRemotingProtocolVersion      2.1


Success!

Monday, April 2, 2012

Revisiting PowerShell Remoting

In a previous post, I mentioned configuring PowerShell Remoting so that I could start doing nifty PowerShell scripting stuffs without having to RDP into the server to do it. You know, The UNIX Way™. As I mentioned in a subsequent post, my remoting had suddenly and mysteriously broken such that while I could run around the filesystem all day long, executing a SharePoint cmdlet on the remote system would turn my session Broken.

Processing data for a remote command failed with the following error message: The WSMan provider host process did not return a proper response.  A provider in the host process may have behaved improperly. For more information, see the about_Remote_Troubleshooting Help topic.

Well, that's unhelpful, since I thought I had fixed this in the past. I spent an hour or so wrangling with it, disabling and re-enabling WSManCredSSP, but what finally tipped me off to the underlying cause of the problem was running winrm s winrm/config/client. I don't have the string output from the command handy since I have since closed that PS window, but in essence it told me that I was getting an underlying firewall exception because one of my NICs was set to Public Network (the VirtualBox Host-Only Network adapter). What? I thought we fixed that!

Opening up the Network and Sharing Center told me that, lo and behold, I was back on a Public network! It didn't take long for me to figure out what had happened - an upgraded version of VirtualBox likely removed and replaced the virtual NIC, and so it was set back to a Public Network. Bollocks!

Well, I fixed that problem, but I was still experiencing the same error trying to run SharePoint cmdlets (this was the point where I restarted the PowerShell session to clear my head), and then it occurred to me that I might need to restart WinRM in order to effect this change (other articles say to disable/enable the NIC, but I did not find this necessary). So, I restarted the WinRM service, and then I ran Get-SPServiceApplication about 10 times just to make sure I wasn't deceiving myself in that the commands were working...

Well, with that finally fixed, now I can set about configuring my remote profiles so that I will never, ever need to retype Add-PSSnapIn Microsoft.SharePoint.PowerShell...

Thursday, March 15, 2012

Setting up PS Remoting for all that PS/SP fun on your local box

With the boss out today and no major tasks beating down my door, I decided to take another swing at getting a sane and working PowerShell environment set up on my work PC. I was tired of having to remote into a server to do simple CLI stuff like getting internal field names and web GUIDs and the like.

The first time I was doing this, I was messing around with PowerGUI and trying to see about simple list iterations on the server, but I rapidly ran out of remote sessions and didn't know offhand how to easily reconnect to them. So, after five times, I had to start figuring out how to kill the remaining sessions before I could continue trying to figure out how to do actual things. Wasn't getting to be very fun.

Now for Take Two.

This time, I learned right away that I could easily manage the sessions by storing them in a variable and using Invoke-Command to do single-line command entry, like so:
PS > $session = New-PSSession -ComputerName TargetHost
PS > Invoke-Command -Session $session -ScriptBlock { Get-ChildItem C:\ } 
Simple enough so far. But obviously cumbersome. I'd already played around with Enter-PSSession before though, so was it easy enough to simply write Enter-PSSession $session?
PS > Enter-PSSession $session
[TargetHost]: PS > Get-ChildItem C:\
Apparently so! Exit-PSSession will return you to your local host, with $session intact, and you can run Remove-PSSession $session once you're finished to clean up after yourself.


Now then, I can at last experiment without worrying about locking myself out.


So, for the new user, it doesn't take long to figure out that while you're on a SharePoint server, the regular PowerShell just won't do. You must either launch the SP-provided PowerShell launcher, or run the basic PowerShell client and then run Add-PSSnapIn Microsoft.SharePoint.PowerShell to get access to the SharePoint-specific cmdlets. So, what happens when I run this in my session?
Cannot access the local farm. Verify that the local farm is properly configured, currently available, and that you have the appropriate permissions to access the database before trying again.
Now, I have this permssion on my own account when I run the PowerShell client via RDP on the server, so what gives? Enter this excellent article from JoshGav which explains that the culprit is our old nemesis, the double-hop authentication problem! Curses!

Following the instructions within mostly worked, but running Enable-WSManCredSSP -Role Client -DelegateComputer * -Force would always fail with the following:
Enable-WSManCredSSP : The client cannot connect to the destination specified in the request. Verify that the service on the destination is running and is accepting requests. Consult the logs and documentation for the WS-Management service running on the destination, most commonly IIS or WinRM. If the destination is the WinRM service, run the following command on the destination to analyze and configure the WinRM service: "winrm quickconfig".
At line:1 char:20
+ Enable-WSManCredSSP <<<<  -Role Client -DelegateComputer CSLISP2010WFE -Force
    + CategoryInfo          : InvalidOperation: (System.String[]:String[]) [Enable-WSManCredSSP], InvalidOperationException
    + FullyQualifiedErrorId : WsManError,Microsoft.WSMan.Management.EnableWSManCredSSPCommand
A couple of MSDN forum postings later, and I got to the point where I confirmed that the problem was that I had enabled remoting on the server...but silly me, I still needed to enable it on the client!
PS C:\Users\Me> Enable-PSRemoting

WinRM Quick Configuration
Running command "Set-WSManQuickConfig" to enable this machine for remote management through WinRM service.
 This includes:
    1. Starting or restarting (if already started) the WinRM service
    2. Setting the WinRM service type to auto start
    3. Creating a listener to accept requests on any IP address
    4. Enabling firewall exception for WS-Management traffic (for http only).

Do you want to continue?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y
WinRM has been updated to receive requests.
WinRM service type changed successfully.
WinRM service started.

Set-WSManQuickConfig : WinRM firewall exception will not work since one of the network connection types on this machine is set to Public. Change the network connection type to either Domain or Private and try again.
At line:50 char:33
+             Set-WSManQuickConfig <<<<  -force
    + CategoryInfo          : InvalidOperation: (:) [Set-WSManQuickConfig], InvalidOperationException
    + FullyQualifiedErrorId : WsManError,Microsoft.WSMan.Management.SetWSManQuickConfigCommand
Aha. I had read on this earlier in my research, and I already knew the culprit - VirtualBox. Luckily, I located this helpful article by Thijs Kroesbergen which details how to exclude VirtualBox from network management so that I can enable CredSSP.

So, now we're golden right? Wrong. Now, when I try to run Enable-PSRemoting, I get sent back a simple "Access is denied." error. Well, that's just peachy. One last bit of research tells me that I must be *explicitly* placed in my machine's local Administrators group.
Windows PowerShell
Copyright (C) 2009 Microsoft Corporation. All rights reserved.

PS C:\Users\Me> Enable-PSRemoting

WinRM Quick Configuration
Running command "Set-WSManQuickConfig" to enable this machine for remote management through WinRM service.
 This includes:
    1. Starting or restarting (if already started) the WinRM service
    2. Setting the WinRM service type to auto start
    3. Creating a listener to accept requests on any IP address
    4. Enabling firewall exception for WS-Management traffic (for http only).

Do you want to continue?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): a
WinRM already is set up to receive requests on this machine.
WinRM already is set up for remote management on this machine.
PS C:\Users\Me> Enable-WSManCredSSP -Role Client -DelegateComputer * -Force


cfg         : http://schemas.microsoft.com/wbem/wsman/1/config/client/auth
lang        : en-US
Basic       : true
Digest      : true
Kerberos    : true
Negotiate   : true
Certificate : true
CredSSP     : true



PS C:\Users\Me> $session = New-PSSession SPSERVER -Authentication CredSSP -Credential DOMAIN\Me
PS C:\Users\Me> Enter-PSSession $session
[spserver]: PS C:\Users\Me\Documents> Add-PSSnapIn Microsoft.SharePoint.PowerShell
[spserver]: PS C:\Users\Me\Documents> $site = Get-SPSite("https://my.site.com/")
[spserver]: PS C:\Users\Me\Documents> $site

Url
---
https://my.site.com
Success!

Tuesday, February 28, 2012

The other migration: STSADM to PowerShell

So, as everyone and their brother knows, while STSADM is still technically available in SP2010, it's deprecated and the general advice is to pretend it doesn't exist anymore. Fair enough, PowerShell purports to give us plenty of power to make our lives better and easier. I haven't done any serious learning with PS yet, but I've had to use it in a couple different ways during the course of this SP2010 migration.

So, that said, looks like it still needs a bit of tweaking:
This is an error message I received because I was re-adding a solution too quickly. I'm uploading the solution on my WFE, but doing deployment/retraction in Central Admin on the application server, and I'll receive the above unfriendly red text whenever I try to upload a solution that's in the middle of removal from the farm. 

Add-SPSolution : An object in the SharePoint administrative framework, "SPSolutionLanguagePack Name=0", depends on other objects which do not exist.  Ensure that all of the objects dependencies are created and retry this operation.
At line:1 char:15
+ Add-SPSolution <<<<  D:\My.New.Column.wsp
    + CategoryInfo          : InvalidData: (Microsoft.Share...dletAddSolution:SPCmdletAddSolution) [Add-SPSolution], A
   rgumentException
    + FullyQualifiedErrorId : Microsoft.SharePoint.PowerShell.SPCmdletAddSolution


Awfully cryptic, but at least it didn't take long to figure out.