I have an existing system that we are cleaning up with thousands of document libraries and moving the contents into Azure instead. I'm looking for a programmatic way to iterate over the lists to find ones that are empty and delete them. Does anyone have any samples (preferably using CSOM or powershell) to accomplish this that they would be willing to share?
At this point, I've come up with the following code to accomplish the task, however I'm getting the error "the underlying connection was closed: an unexpected error occurred on a receive" trying to load the lists due to a timeout because I have so many lists. Here's my solution so far hiding the user secrets of uid, pws, site.
var cred = new SharePointOnlineCredentials(uid, pws);
var context = new ClientContext(site);
context.Credentials = cred;
context.RequestTimeout = Timeout.Infinite;
var lists = context.Web.Lists;
context.Load(lists);
context.ExecuteQuery();
foreach(var list in lists)
{
list.DeleteObject();
list.Update();
}
Try the code below, all you have to do here is to specify the lists you want to ignore, some system lists are listed already, make sure you are not deleting important stuff from SharePoint.
The script below uses SharePoint Online Management Shell and PnP PowerShell, download and install both before running the script:
https://www.microsoft.com/en-us/download/details.aspx?id=35588
https://github.com/SharePoint/PnP-PowerShell
cls
$url = "https://YOUR-URL-GOES-HERE.sharepoint.com"
if ($cred -eq $null)
{
$cred = Get-Credential
Connect-PnPOnline $url -Credentials $cred
}
$CSOM_context = New-Object Microsoft.SharePoint.Client.ClientContext($url)
$CSOM_credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($cred.UserName, $cred.Password)
$CSOM_context.Credentials = $CSOM_credentials
$lists = $CSOM_context.Web.Lists
$CSOM_context.Load($lists)
$CSOM_context.ExecuteQuery()
$ignoreList = "Form Templates", "Site Assets", "Style Library", "_catalogs/hubsite", "Translation Packages"
$lists | ? { $_.ItemCount -eq 0 -and $_.BaseTemplate -eq 101 -and $_.Title -inotin $ignoreList} | % {
Write-Host "- " $_.Title -NoNewline
try {
Remove-PnPList -Identity $_.Title -Force
Write-Host -ForegroundColor Green " [deleted] " `n
}
catch [Execption] {
Write-Host -ForegroundColor Red " [FAILURE] - " $_.Exception.Message `n
}
}
I don't think so you need any timeout over here you can use below code to delete an empty document library
using Microsoft.SharePoint.Client;
using System;
using System.Linq;
using System.Security;
namespace DeleteAllEmptyDocumentLibrary
{
class Program
{
static void Main(string[] args)
{
ListItemCollection itemCollection = null;
SecureString pswd = new SecureString();
try
{
// Site Url to scan
string webUrl = "https://SiteUrl";
string userName = "UserName";
string password = "Password";
using (ClientContext context = new ClientContext(webUrl))
{
foreach (char c in password.ToCharArray())
pswd.AppendChar(c);
// Setting credential for the above site
context.Credentials = new SharePointOnlineCredentials(userName, pswd);
context.ExecuteQuery();
var lists = context.LoadQuery(context.Web.Lists.Where(l => l.BaseType == BaseType.DocumentLibrary));
context.ExecuteQuery();
foreach (List list in lists)
{
try
{
// Getting all items from selected list using caml query
itemCollection = list.GetItems(CamlQuery.CreateAllItemsQuery());
//Loading selected list items
context.Load(itemCollection);
context.ExecuteQuery();
// Looping each item
if (itemCollection != null && itemCollection.Count == 0)
{
list.DeleteObject();
context.ExecuteQuery();
}
}
catch (Exception ex)
{ }
}
}
}
catch (Exception ex) { }
}
}
}
Related
I'm trying to loop through an array of servers and run this test path command as a background thread. It needs to be a thread because I will be monitoring it for a time out and killing the thread if it exceeds that time out.
I cannot get a variable return value, nor do I know how I would have separate variables for multiple machines or if I'm passing the variables properly. In c# I would use a thread collection, but I can't seem to figure out the syntax for Powershell.
foreach ($server in $servers)
{
Write-Host $server;
$scriptBlock =
{
$returnVal = Test-Path "\\$server\c$";
return $returnVal;
}
$remoteReturnVal = Invoke-Command -ComputerName [Environment]::MachineName -ScriptBlock { $script } –AsJob;
}
Here is how I can do it in Powershell with c# (the below works)
$shareCheck =
#'
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace powershellConsoleForm
{
public class shares
{
public List<string> shareExists(List<string> servers, int timeOut)
{
List<string> returnValue = new List<string>();
foreach (string server in servers)
{
string path = #"\\" + server + #"\c$";
Func<bool> func = () => Directory.Exists(path);
Task<bool> task = new Task<bool>(func);
task.Start();
if (task.Wait(timeOut))
{
//return task.Value;
Console.Write(" ");
Console.Write("success " + task.Result);
Console.Write(" ");
returnValue.Add(server + "|" + task.Result.ToString());
}
else
{
Console.Write("timeout");
returnValue.Add(server + "|timeout");
}
}
Console.Write("Done");
return returnValue;
}
}
}
'#
try
{
$shareChecker = New-Object powershellConsoleForm.shares;
}
catch
{
$assemblies = ("System", "System.Collections", "System.ComponentModel", "System.Data", "System.Drawing", "System.IO", "System.Linq", "System.Management.Automation", "System.Security", "System.Threading.Tasks", "System.Windows.Forms", "System.Threading", "System.Collections.Concurrent", "System.Security.Principal");
Add-Type -TypeDefinition $shareCheck -ReferencedAssemblies $assemblies -Language CSharp
$shareChecker = New-Object powershellConsoleForm.shares;
}
$servers = #("server1", "server2", "server3", "server4", "server5");
$timeOut = 11000;
[int] $counter = 0;
do
{
$counter ++;
Write-Host $counter;
$shareAvailibilities = $shareChecker.shareExists($servers, $timeOut);
foreach ($shareAvailibility in $shareAvailibilities)
{
Write-Host $shareAvailibility;
}
Write-Host " ";
Write-Host " ";
Sleep 5;
}while ($true)
First thing, Invoke-Command by defaults returns the output of the scriptblock. So your scriptblock could be something like $ScriptBlock = {Test-Path "\\$server\c$"}.
Then, there are several issues with your Invoke-Command line.
Since you're using -AsJob, you don't need to assign output to a variable. You would access the results of your job with Get-Job.
Then you could add -JobName $server so it's easier to view the results by server.
Remove -ComputerName as it runs on localhost by default. As a side note, I personally like using $env:COMPUTERNAME instead as it's a bit easier on the eyes, and to access.
You have wrapped your $script variable inside of a scriptblock, when it's already a script block. Remove the curly braces. But, since the command is so simple, it might make more sense to just keep the braces, and replace variable with the command.
here's a revised copy of your script, based on my adjustments (and a couple formatting tweaks):
foreach ($server in $servers) {
Write-Host $server
Invoke-Command -ScriptBlock { Test-Path "\\$server\c$" } –AsJob -JobName $server
}
Get-Job
I was thinking about it, and since you're just running these on the local machine, it would make a lot more sense to just use Start-Job:
foreach ($server in $servers) {
Write-Host $server
Start-Job -Name $server -ScriptBlock { Test-Path "\\$server\c$" }
}
Get-Job
Then to get the results of the jobs themselves you can use Receive-Job. Either pipe Get-Job to Receive-Job, or access each one by name Receive-Job -Name <servername>
I have a script which creates users in Microsoft Exchange Server and Active Directory. So, though it's commmon that user's names have accents or ñ in Spain, I want to avoid them for the username to not to cause any incompatibilities in old systems.
So, how could I clean a string like this?
$name = "Ramón"
To be like that? :
$name = "Ramon"
As per ip.'s answer, here is the Powershell version.
function Remove-Diacritics {
param ([String]$src = [String]::Empty)
$normalized = $src.Normalize( [Text.NormalizationForm]::FormD )
$sb = new-object Text.StringBuilder
$normalized.ToCharArray() | % {
if( [Globalization.CharUnicodeInfo]::GetUnicodeCategory($_) -ne [Globalization.UnicodeCategory]::NonSpacingMark) {
[void]$sb.Append($_)
}
}
$sb.ToString()
}
# Test data
#("Rhône", "Basíl", "Åbo", "", "Gräsäntörmä") | % { Remove-Diacritics $_ }
Output:
Rhone
Basil
Abo
Grasantorma
Well I can help you with some of the code.....
I used this recently in a c# project to strip from email addresses:
static string RemoveDiacritics(string input)
{
string inputFormD = (input ?? string.Empty).Normalize(NormalizationForm.FormD);
StringBuilder sb = new StringBuilder();
for (var i = 0; i < inputFormD.Length; i++)
{
UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(inputFormD[i]);
if (uc != UnicodeCategory.NonSpacingMark)
{
sb.Append(inputFormD[i]);
}
}
return (sb.ToString().Normalize(NormalizationForm.FormC));
}
I guess I can now say 'extending into a PowerShell script/form is left to the reader'.... hope it helps....
Another PowerShell translation of #ip for non C# coders ;o)
function Remove-Diacritics
{
param ([String]$sToModify = [String]::Empty)
foreach ($s in $sToModify) # Param may be a string or a list of strings
{
if ($sToModify -eq $null) {return [string]::Empty}
$sNormalized = $sToModify.Normalize("FormD")
foreach ($c in [Char[]]$sNormalized)
{
$uCategory = [System.Globalization.CharUnicodeInfo]::GetUnicodeCategory($c)
if ($uCategory -ne "NonSpacingMark") {$res += $c}
}
return $res
}
}
Clear-Host
$name = "Un été de Raphaël"
Write-Host (Remove-Diacritics $name )
$test = ("äâûê", "éèà", "ùçä")
$test | % {Remove-Diacritics $_}
Remove-Diacritics $test
With the help of the above examples I use this "one-liner:" in pipe (tested only in Win10):
"öüóőúéáűí".Normalize("FormD") -replace '\p{M}', ''
Result:
ouooueeui
PS> [Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding(1251).GetBytes("Ramón"))
Ramon
PS>
Another solution... quickly "reuse" your C# in PowerShell (C# code credits lost somewhere on the net).
Add-Type -TypeDefinition #"
using System.Text;
using System.Globalization;
public class Utils
{
public static string RemoveDiacritics(string stIn)
{
string stFormD = stIn.Normalize(NormalizationForm.FormD);
StringBuilder sb = new StringBuilder();
for (int ich = 0; ich < stFormD.Length; ich++)
{
UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(stFormD[ich]);
if (uc != UnicodeCategory.NonSpacingMark)
{
sb.Append(stFormD[ich]);
}
}
return (sb.ToString().Normalize(NormalizationForm.FormC));
}
}
"# | Out-Null
[Utils]::RemoveDiacritics("ABC-abc-ČŠŽ-čšž")
Instead of creating a stringbuilder and looping over characters, you can just use -replace on the NFD string to remove combining marks:
function Remove-Diacritics {
param ([String]$src = [String]::Empty)
$normalized = $src.Normalize( [Text.NormalizationForm]::FormD )
($normalized -replace '\p{M}', '')
}
I was wondering if I can make a comment somewhere in Team Foundation Server when I add an AD user to a TFS group or change the group of the user, for auditing purpose.
I have created a PowerShell script to record day2day changes to TFS user databases and query the AD to find out who approved this change.
Now we have a self made db.
For what it's worth - I wrote a C# method to pull the list of all members from every TFS group. Hope that helps!
EDIT: "This is posted as an answer because "xidada" was going to write a script to pull the information and since I already had the code to get the information that needed, I thought the code would be a guide to help him/her with the script"
private void btn_GetNow_Click()
{
TfsTeamProjectCollection tfs = new TfsTeamProjectCollection(new Uri("http://server/collection"));
tfs.EnsureAuthenticated();
TfsConfigurationServer srv = tfs.ConfigurationServer;
CatalogNode configurationServerNode = srv.CatalogNode;
// Query the children of the configuration server node for all of the team project collection nodes
ReadOnlyCollection<CatalogNode> tpcNodes = configurationServerNode.QueryChildren(
new Guid[] { CatalogResourceTypes.ProjectCollection },
false,
CatalogQueryOptions.None
);
Guid tpcId = new Guid(tpcNodes[0].Resource.Properties["InstanceId"]);
TfsTeamProjectCollection tpc = srv.GetTeamProjectCollection(tpcId);
// get a reference to the work item tracking service
var workItemStore = tpc.GetService<WorkItemStore>();
List<Identity> result = new List<Identity>();
// iterate over the projects
foreach (Project project in workItemStore.Projects)
{
Console.WriteLine("\tProject: {0}", project.Name);
try
{
VersionControlServer versionControl = (VersionControlServer)tpc.GetService(typeof(VersionControlServer));
TeamProject teamProject = versionControl.GetTeamProject(project.Name);
IGroupSecurityService gss = (IGroupSecurityService)tpc.GetService<IGroupSecurityService>();
Identity[] appGroups = gss.ListApplicationGroups(teamProject.ArtifactUri.AbsoluteUri);
foreach (Identity group in appGroups)
{
rtb_Users.AppendText(group.DisplayName + "\n");
Identity[] groupMembers = gss.ReadIdentities(SearchFactor.Sid, new string[] { group.Sid }, QueryMembership.Expanded);
foreach (Identity member in groupMembers)
{
if (member.Members != null)
{
foreach (string memberSid in member.Members)
{
Identity memberInfo = gss.ReadIdentity(SearchFactor.Sid, memberSid, QueryMembership.None);
if (memberInfo.Type == IdentityType.WindowsUser)
{
if (!result.Contains(memberInfo))
{
result.Add(memberInfo);
rtb_Users.AppendText("\t\t" + memberInfo.AccountName + " - " + memberInfo.DisplayName + " - " + memberInfo.Domain + "\n");
}
else
{
Console.WriteLine("\t\tUser already available " + memberInfo.AccountName);
}
}
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("\tThe Project: '{0}' throws an exception: {1} and will be ignored.", project.Name, ex.Message);
}
}
}
This method is pulled out from my application AS IS and would need to be customized for your needs.
I got a lot of sites with document libraries where the "Make "New Folder" command available" option is set to "no".
I would like to walk through these document libraries and change this to "yes".
How can I achieve this? After doing some search, I found that a lot of things can be done with the files in a document library, but did not find any example that shows how to change settings (advanced settings) of the library itself.
Thank you,
vm
Since you are looking for solution that utilizes CSOM API, the below example demonstrates how enable folders for document libraries:
using (var ctx = new ClientContext(webUri))
{
var result = ctx.LoadQuery(ctx.Web.Lists.Where(l => l.BaseType == BaseType.DocumentLibrary && !l.Hidden));
ctx.ExecuteQuery();
foreach (var list in result)
{
list.EnableFolderCreation = true;
list.Update();
}
ctx.ExecuteQuery();
}
VB.Net version
Using context As Microsoft.SharePoint.Client.ClientContext = New Microsoft.SharePoint.Client.ClientContext(webUri)
Dim qry = From l In context.Web.Lists
Where (CInt(l.BaseType) = 1) AndAlso Not l.Hidden
Select l
Dim result As IEnumerable(Of Microsoft.SharePoint.Client.List) = context.LoadQuery(qry)
context.ExecuteQuery()
Dim list As Microsoft.SharePoint.Client.List
For Each list In result
list.EnableFolderCreation = True
list.Update()
Next
context.ExecuteQuery()
End Using
Using powershell.
$list.EnableFolderCreation = $true;
$list.update();
put this inside a for loop that iterates through the lists/sites/webs of your farm. something like:
$sc = http://myweb.com/mysitecollection
$spsite = Get-SPsite $sc
foreach ($web in $spsite.AllWebs)
{
foreach ($list in $web.Lists)
{
$list.EnableFolderCreation = $true;
$list.update();
}
}
$spsite.dispose()
if you would rather do this using the client object model, throw this into a console app. (make sure you reference Microsoft.SharePoint.dll)
using System;
using Microsoft.SharePoint;
namespace SharepointModifier
{
class FolderEnabler
{
static void Main(string[] args)
{
string sitecollectionaddress = "Http://mysitecollection.com/";
using (SPSite mysites = new SPSite(sitecollectionaddress))
{
foreach (SPWeb web in mysites.AllWebs)
{
foreach (SPList list in web.Lists)
{
list.EnableFolderCreation = true;
//Make any other changes to list properties here
list.Update();
Console.WriteLine(list.Title + " Has been updated.");
}
}
}
}
}
}
I'm getting a stuck. Suppose that I create 5 sites. For each one, I create a few sharepoint groups. So, I create a dropdownlist control to bind 5 sites and when I click on any site there, I will get sharepoint groups created on it. But I always see the dropdownlist used to bind these groups still never change. I mean it only binds a few default groups in sahrepoint everytime. The new created groups is not.
And I have confusion like this
web.AssociatedGroups
web.Groups
web.SiteGroups
which one we will use this case ? Please guide me
Here my snippet code
private void BindSPGroupsToDropDownList(DropDownList ddl, string siteUrl)
{
ddl.Items.Clear();
try
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = new SPSite(siteUrl))
{
using (SPWeb web = site.OpenWeb())
{
//web.SiteGroups
foreach (SPGroup spGroup in web.Groups)
{
ddl.Items.Add(new ListItem(spGroup.Name.Trim(), spGroup.ID.ToString()));
}
}
}
});
}
}
thanks in advance
You don't show how you add groups, so it is hard to see why you can't list them.
Some considerations:
groups are scoped at the site collection level, i.e. there is no notion of groups at the web (SPWeb) level; only at the SPSite level
I wrote code a couple of years ago for managing groups, so I don't remember the difference between the different properties. Looking at my code, I used SPWeb.SiteGroups to test if a group exists and to add a new group.
Here is my - slightly anonymized - code:
public static void CreateSiteGroup(SPSite site, string strGroupName, string strGroupDesc, string strGroupOwner) {
if (site == null) {
string message = GetMessagePrefix() + " Site is null";
XxxLog.Error(XxLogEventId.Common, message, XxxLogCategory.CommonBusiness);
} else if (String.IsNullOrEmpty(strGroupName)) {
string message = GetMessagePrefix() + " The group name is empty";
XxxLog.Error(XxxLogEventId.Common, message, XxxLogCategory.CommonBusiness);
} else {
try {
using (SPWeb rootWeb = site.RootWeb) {
SPMember owner;
if (String.IsNullOrEmpty(strGroupOwner)) {
owner = rootWeb.CurrentUser;
} else {
if (!ContainsGroup(site, strGroupOwner)) {
string message = GetMessagePrefix() + " Can not find owner group name: " + strGroupOwner;
XxxLog.Error(XxxLogEventId.Common, message, XxxLogCategory.CommonBusiness);
return;
} else {
owner = rootWeb.SiteGroups[strGroupOwner];
}
}
if (!ContainsGroup(site, strGroupName)) {
rootWeb.SiteGroups.Add(strGroupName,
owner,
null, // no default user
strGroupDesc);
} else {
string message = GetMessagePrefix() + " The group " + strGroupName + " was already present";
XxxLog.Info(message, XxxLogCategory.CommonBusiness);
}
}
} catch (Exception e) {
string message = GetMessagePrefix() + " Cannot create " + strGroupName + " group";
XxxLog.Error(XxxLogEventId.Common, message,e, XxxLogCategory.CommonBusiness);
}
}
}
public static Boolean ContainsGroup(SPSite site, string name) {
SPGroup group = null;
using (SPWeb rootWeb = site.RootWeb) {
foreach (SPGroup g in rootWeb.SiteGroups) {
if (g.Name.ToUpper().Equals(name.ToUpper())) {
group = g;
break;
}
}
}
return (group != null);
}