Due to some extenuating circumstances we had to resort to migrating a teams content from one farm to another via locally downloading the files from their lists and shipping them. Unfortunately, this team used folders in their lists VERY heavily. And they wanted to maintain their folder structure for document organization purposes, so I had to come up with a script that could download the contents of a list, folders and all, while also maintaining that folder structure. I have years of SP admin experience, but my PS scripting has always been very basic to intermediate usage at best so I am reaching out to you all to help me optimize this script.
add-pssnapin microsoft.sharepoint.powershell
$w=get-spweb "http://URL"
$tlfolder=$w.getfolder("Old_Surveys")
$subfold=$tlfolder.subfolders|select *|?{$_.name -match "historical"}
$subfolders=$subfold.subfolders
foreach ($subfolder in $subfolders)
{
new-item -path "D:\backups\Old_Surveys\Historical Company Surveys" -name $subfolder.name -itemtype "directory"
#if the 2 level sub folder exists
if ($subfolder.subfolders -ne $null)
{
$subfoldername=$subfolder.name
$subsubfolders=$subfolder.subfolders
foreach ($subsubfolder in $subsubfolders)
{
new-item -path "D:\backups\Old_Surveys\Historical Company Surveys\$subfoldername" -name $subsubfolder.name -itemtype "directory"
# if the 3 level sub folder exists
if ($subsubfolder.subfolders -ne $null)
{
$subsubfoldername=$subsubfolder.name
$subsubsubfolders=$subsubfolder.subfolders
foreach ($subsubsubfolder in $subsubsubfolders)
{
new-item -path "D:\backups\Old_Surveys\Historical Company Surveys\$subfoldername\$subsubfoldername" -name $subsubsubfolder.name -itemtype "directory"
#if the 3 level sub files exists
if ($subsubsubfolder.files -ne $null)
{
$subsubsubfiles=$subsubsubfolder.files
$subsubsubfoldername=$subsubsubfolder.name
foreach ($subsubsubfile in $subsubsubfiles)
{
write-host $subsubsubfile.name
$b=$subsubsubfile.openbinary()
$fs=new-object system.io.filestream(("d:\backups\Old_Surveys\Historical Company Surveys\$subfoldername\$subsubfoldername\$subsubsubfoldername"+$subsubsubfile.name), [system.io.filemode]::create)
$bw=new-object system.io.binarywriter($fs)
$bw.write($b)
$bw.close()
}
}
}
}
if ($subsubfolder.files -ne $null)
{
$subsubfiles=$subsubfolder.files
$subsubfoldername=$subsubfolder.name
foreach ($subsubfile in $subsubfiles)
{
write-host $subsubfile.name
$b=$subsubfile.openbinary()
$fs=new-object system.io.filestream(("d:\backups\Old_Surveys\Historical Company Surveys\$subfoldername\$subsubfoldername\"+$subsubfile.name), [system.io.filemode]::create)
$bw=new-object system.io.binarywriter($fs)
$bw.write($b)
$bw.close()
}
}
}
if ($subfolder.files -ne $null)
{
$subfiles=$subfolder.files
$subfoldername=$subfolder.name
foreach ($subfile in $subfiles)
{
write-host $subfile.name
$b=$subfile.openbinary()
$fs=new-object system.io.filestream(("d:\backups\Old_Surveys\Historical Company Surveys\$subfoldername\"+$subfile.name), [system.io.filemode]::create)
$bw=new-object system.io.binarywriter($fs)
$bw.write($b)
$bw.close()
}
}
}
}
The objective of the code was to iterate down thru any folders, and subfolders of the folders, and create them on the local drive and download any files that are in them at that level. I did this manually for up to 3-sub levels of folders, but if there are more levels lower than that then I would have had to keep manually adding all the code and going even longer. Surely there has to be a better way of doing this?
Related
I am trying loop through a text file with a list of folder names I would like to create. Then also create a network share to the folder as well.
I am able to create the folder but I am getting stuck on creating the network share.
$folder ='\\networkserver\D$' #Root Directory to place the New folders in.
$routes = get-content 'C:\uncroutes.txt'
foreach ($routes in $routes) {
$newpath = Join-Path "$folder\" -ChildPath $routes
New-Item $newpath -type Directory
foreach ($newpath in $newpath) {
New-SmbShare -Name $newpath -Path $folder -FullAccess Administrator
}
}
This is the error message:
New-SmbShare -Name $newpath -Path $folder -FullAccess Adminis ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (MSFT_SMBShare:ROOT/Microsoft/Windows/SMB/MSFT_SMBShare) [New-SmbShare], CimException
+ FullyQualifiedErrorId : Windows System Error 5,New-SmbShare
Got it working with Help from #Adminofthings. Here is the working code.
$folder ='\\networkserver\D$' #Root Directory to place the New folders in.
$routes = get-content 'C:\uncroutes.txt' #list of folder Names
foreach ($route in $routes) {
$newpath = Join-Path "$folder\" -ChildPath $route
New-Item $newpath -type Directory
foreach ($ShareName in $ShareNames) {
$ShareName = ($route | sls -pattern "([0-9a-zA-Z-_ ]+)$").matches.value
$serverpath = "d:\$route"
New-SmbShare -Name $ShareName -Path $serverpath -FullAccess Administrator
}
}
If I assume correctly about uncroutes.txt and say that it contains a unc path on each line, then you can get the share name by using something like the code below:
$ShareName = ($route | sls -pattern "([0-9a-zA-Z-_ ]+)$").matches.value
Then pass $ShareName into your -name parameter.
I have a Powershell Script I'm working on for post-migration SSRS report administration tasks.
In this particular scenario we have a DEV environment (where I've been primarily testing) which hosts a single instance of SSRS, and a Prod environment which is a scaled out deployment across 4 nodes.
I'm new to Powershell (just discovered it 2 days ago...) and the script I have is pretty simple:
Clear-Host
$Username = "domain\myUsername"
$Password = "myPassword"
$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList #($Username,(ConvertTo-SecureString -String $Password -AsPlainText -Force))
# Dev Connection String
$webServiceUrl = 'http://DEVwebServer.domain.com/reportserver/reportservice2010.asmx?WSDL'
# Prod Connection String
# $webServiceUrl = 'http://PRODwebServerNode1.domain.com/reportserver/reportservice2010.asmx?WSDL'
$rs = New-WebServiceProxy -Uri $webServiceUrl -Credential $Cred
$reports = $rs.ListChildren("/Some Folder Under Root", $true) | Where-Object { $_.TypeName -eq "Report" }
$type = $ssrsProxy.GetType().Namespace;
$schedDefType = "{0}.ScheduleDefinition" -f $type;
$schedDef = New-Object ($schedDefType)
$warning = #();
foreach ($report in $reports) {
$sched = $rs.GetExecutionOptions($report.Path, [ref]$schedDef);
$snapShotExists = $rs.ListItemHistory($report.Path);
if($sched -eq "Snapshot") {
Write-Host "Following report is configured to run from Snapshot:" -ForegroundColor Yellow
Write-Host ("Report Name: {0}`nReport Path: {1}`nExecution Type: {2}`n" -f $report.Name, $report.Path, $sched)
if ($snapShotExists) {
Write-Host "Does Snapshot Exist..?`n" -ForegroundColor Yellow
Write-Host "Yes!`tNumber of Snapshots: " $snapShotExists.Count -ForegroundColor Green
$snapShotExists.CreationDate
Write-Host "`n------------------------------------------------------------"
}
elseif (!$snapShotExists) {
Write-Host "Does Snapshot Exist..?`n" -ForegroundColor Yellow
Write-Host ("No!`n") -ForegroundColor Red
Write-Host "Creating Snapshot.......`n" -ForegroundColor Yellow
$rs.CreateItemHistorySnapshot($report.Path, [ref]$warning);
Write-Host "Snapshot Created!`n" -ForegroundColor Green
$snapShotExists.CreationDate
Write-Host "`n------------------------------------------------------------"
}
}
}
The purpose of the script is simply to recursively iterate over all of the reports for the given folder in the $reports variable, check to see if the execution type is set to "Snapshot", if it is check to see if a "History Snapshot" exists, and if one does not exist, create one.
When I run this in Dev it works just fine, but when I run in PROD I get the following error repeated for each $report in my foreach loop:
Any ideas on why this would work in one and not the other and how to overcome this error?
I was able to get this working on the Prod instance by making some adjustments using this answer as a guide:
By updating my call to New-WebServiceProxy to add a Class and Namespace flag, I was able to update the script in the following ways:
...
# Add Class and Namespace flags to New-WebServiceProxy call
$rs = New-WebServiceProxy -Class 'RS' -Namespace 'RS' -Uri $webServiceUrl -Credential $Cred
$reports = $rs.ListChildren("/Business and Technology Solutions", $true) | Where-Object { $_.TypeName -eq "Report" }
# Declare new "ScheduleDefintion object using the Class declared in the New-WebServiceProxy call
$schedDef = New-Object RS.ScheduleDefinition
$warning = #();
foreach ($report in $reports) {
# Referencing the "Item" property from the ScheduleDefinition
$execType = $rs.GetExecutionOptions($report.Path, [ref]$schedDef.Item)
...
I don't think the adding of the Class and Namespace flags on the New-WebServiceProxy call was exactly what did it, as I think it's just a cleaner way to ensure you're getting the proper Namespace from the WebService. Maybe just a little sugar.
I think the key change was making sure to the "Item" property from the schedule definition object, although I'm not sure why it was working in Dev without doing so...
Currently I'm using, SHIFT + Right Click > Open powershell window here > then paste in
dir -recurse | select -ExpandProperty Name | foreach {$_.split('.',2)[0]} | out-file file.txt
Only problem is I choose a directory to SHIFT + Right click, but I get all the names of the files/folders inside the second folder too and it really ruins the organization I'm going for.
So for example I have a folder called "RootFolder".
Inside RootFolder is 10 other folders called "Folder1" through "Folder10".
I only want the names Folder1 - Folder10 to be inside a .txt folder the shell command creates. I do not want Subfolders/files inside folders Folder1-10 in the .txt file.
If you want to only list directories you need to tell it.
Dir -R | ? { $_.PSIsContainer }
or
Dir -R | ? { $_ -is [System.IO.DirectoryInfo] }
In more recent PowerShell versions you can do it directly.
Dir -Dir
Dir = Get-ChildItem
-R = -Recurse
? = Where-Object
-Dir = -Directory
If you only want directories within the named directory, you don't want to recurse. That will travel the entire directory hierarchy; just use the -Directory switch to filter the output to only include directories.
Then, you can just extract the Name property from the DirectoryInfo objects.
PS C:\> dir C:\Windows -Directory |% { $_.Name }
addins
appcompat
apppatch
AppReadiness
[...]
and output as desired. Even more concisely:
(dir C:\Windows -Directory).Name
I have a solution file that includes some projects inside and I'd like to delete some of them using PowerShell.
The aim is to delete a block of text that contains a string (let's say "abcxyz") starting with "Project" and ends with "EndProject" in the next line (or more than that).
For example:
Project("{1111-2222-FFFF-3333}") = "AutoRun", "..\generate\Infra\generate\generate.csproj", "{999999-UUUUUU-GGGGGG-ABCDEFGH}"
EndProject
Project("{5555-2222-FFFF-3333}") = "SetupSec", "..\generate\Setup.csproj", "{999999-UUUUUU-GGGGGG-ABCDEFGH}"
EndProject
Project("{4444-2222-FFFF-3333}") = "Common.Fyyy", "..\generate\Infra\Common\Common.csproj", "{999999-UUUUUU-GGGGGG-ABCDEFGH}"
EndProject
Project("{9999-2222-FFFF-3333}") = "Command.Console", "..\generate\Path\Console.csproj", "{999999-UUUUUU-GGGGGG-ABCDEFGH}"
EndProject
Project("{7777-2222-FFFF-3333}") = "Infra.GUI", "..\..\generate\GUI.csproj", "{999999-UUUUUU-GGGGGG-ABCDEFGH}"
ProjectSection(ProjectDependencies) = postProject
{AAAA-2222-FFFF-3333} = {999999-UUUUUU-GGGGGG-ABCDEFGH}
EndProjectSection
EndProject
In the example above, I'd like to remove projects contains the string "Infra" in any case.
Is there a simple way of doing it using PowerShell Regex?
(deletion should be done to entire solution file from Project to EndProject)
Thanks,
Shai.
I came here looking for a similar solution. What I did was create this powershell function. It may just be a good jumping off point where you can tailor to your more specific requirements, but it works for me. Enjoy...
function Remove-SourceControl() {
param (
[string]$sourceFilePathname
)
$tempFile = New-TemporaryFile
$filterOn = $false
$fileVersionCorrect = $false
$backupFile = "$sourceFilePathname (backup)"
Copy-Item $sourceFilePathname $backupFile
$rowIndex = 1
Get-Content -Path:$sourceFilePathname | ForEach-Object {
if ($_ -like "*Microsoft Visual Studio Solution File, Format Version 12.00*" ) {
$fileVersionCorrect = $true
}
if ($fileVersionCorrect -eq $true) {
if ($_ -like "*TeamFoundationVersionControl*") { $filterOn = $true }
if (!$filterOn) {
Write-Output $_ | Out-File -FilePath:$tempFile -Append
}
if ($filterOn -and $_ -like "*EndGlobalSection*") { $filterOn = $false }
} else {
Write-Output $_ | Out-File -FilePath:$tempFile -Append
}
$rowIndex++
}
if ($fileVersionCorrect -eq $true) {
Copy-Item $tempFile $sourceFilePathname -Force
}
Remove-Item $tempFile
}
$path = "c:\some-folder-pathname"
Remove-SourceControl "$path\MySoluton.sln"
Assuming your actual file is consistent with the sample data, you can do this without needing to mess with regex at all:
Get-Content $ProjectFile -Delimiter 'EndProject' |
Where-Object {$_ -notlike '*Infra*'} |
Add-Content $NewProjectFile
That will break up the file into separate projects, and then filter out any of them that contain the string "Infra", and write the rest to a new file.
Your question is "is there a simple way in Powershell" and the answer is: No.
Solution files are a nasty old format with various widely-separated parts that depend on each other. It is possible to edit them by hand but it is hard to get right and easy to get wrong.
If you simply want to delete some projects, open the file in Visual Studio, delete the projects, and save it.
If this question is to solve part of a larger problem, you should ask a new question, how to solve that larger problem.
I have a bunch of servers I have to uninstall an app from. I am using:
$app = Get-WmiObject -Class Win32_Product | Where-Object {
$_.Name -match "application name"
}
$app.Uninstall()
I have tested the above out and it works great. I want to now run this so it uninstalls the app on a bunch of servers. I know I can use the for each option but for some reason I am having an issue getting it to work.
I created a text file called servers and listed my servers in there but it errors out each time.
Does anyone have a good pay to add a for each part to my above uninstall portion so it works?
The -ComputerName parameter of Get-WmiObject accepts a list of computernames.
$servers = Get-Content 'C:\your\computerlist.txt'
Get-WmiObject -Computer $servers -Class Win32_Product |
? { $_.Name -like '*application name*' } |
% { $_.Uninstall() }
Later on the page that you got your original code from, there is an answer from David Setler that does what you need. Slightly modified to fir your scenario:
$computers = Get-Content C:\servers.txt
foreach($server in $computers){
$app = Get-WmiObject -Class Win32_Product -computer $server | Where-Object {
$_.Name -match "application name"
}
$app.Uninstall()
}
Assuming $computers is the list of servers from your servers text file.
I would stay away from win32_product because this class isn't working properly.
See https://support.microsoft.com/en-us/kb/974524.
Basicly what happens when you query this class is that every msi will trigger a repair an the software package (spamming the eventlog) which in some cases can screw with installed software (I've seen broken installations of some programs after querying this class - though it's rare, you don't want to risk this on production servers).