I am trying to write a PowerShell scripts that accomplishes the following tasks with partial results :
Retrieving a list of SharePoint sites (including OneDrive sites) that contain a certain word in the url (done) ;
Using a ForEach-Object to retrieve the list of owners and users of each site (done);
Exporting these informations in a CSV file (partially done);
My problem is the number 3, I'm trying to make it with one column for the sites URLs, one column for the owner, and the 3rd column with all the users inside,
but unfortunately I'm only able to make the csv with the users list inside, here is the code that brought me to this point :
$username = "username#domain.onmicrosoft.com"
$password = "password" | ConvertTo-SecureString -asPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($username,$password)
$Tenanturl = "https://domain-admin.sharepoint.com/"
# Connessione all'account Admin SharePoint
Connect-SPOService -Url $TenantUrl -Credential $credential
Get-SPOSite -IncludePersonalSite $true -Limit all -Filter "Owner -like 'WordIWantToFilter'" |
ForEach-Object {
Get-SPOUser -Site $_.Url
} |
Select-Object -Property LoginName,UserType |
Export-Csv -Path "C:\Users\User\Desktop\SharePoint_Sites_List.csv" -NoTypeInformation
The result is a CSV file with LoginName,"UserType" in the cell A1, and the related info in the other rows.
What I am trying to accomplish :
First column for the sites URLs,
Second column for the owner of the sites,
and the 3rd column with all the users of each site inside.
I know I am missing a lot of stuff, but I'm not a developer whatsoever :),
these are some of the links I used to make this code,
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/export-csv?view=powershell-7
https://techcommunity.microsoft.com/t5/sharepoint/list-all-site-collection-admins-powershell/m-p/264135
what should I look for ? I'm looking for tips or even just little pieces of code,
thanks
I predict you'll run into woes with this structure here:
Get-SPOSite -IncludePersonalSite $true -Limit all -Filter "Owner -like 'WordIWantToFilter'" |
ForEach-Object {
Get-SPOUser -Site $_.Url
} |
Select-Object -Property LoginName,UserType |
Export-Csv -Path "C:\Users\User\Desktop\SharePoint_Sites_List.csv" -NoTypeInformation
These pipes will make things confusing. As an Automation Consultant and Engineer for a variety of firms for five years, I would avoid over-reliance on pipes, as it makes debugging code tricky and error prone.
I would rewrite it like this, minimizing those piped statements:
$filteredSites = Get-SPOSite -IncludePersonalSite $true -Limit all -Filter "Owner -like 'WordIWantToFilter'"
"Found $($filteresSites.Counts) from previous line"
$sitesArray=#() #make empty array to hold results
ForEach($site in $filteredSites){
$sitesArray += Get-SPOUser -Site $_.Url
}
"Have $($sitesArray.Count) sites from previous line"
#good place to debug the output, btw :)
#prepare to export
"Run in the PowerShell ISE, we will have a left-over object `$sitesArray` we can use to test exporting "
$sitesArray | Select-Object -Property LoginName,UserType |
Export-Csv -Path "C:\Users\User\Desktop\SharePoint_Sites_List.csv" -NoTypeInformation
Then to use it, open this all in the PowerShell ISE and run it. You'll get some helpful logging to the console and the ISE leaves some state in time variables behind which makes it really easy to troubleshoot.
These changes will make it easier to determine where you're losing data. For instance, if you run this and see an empty file, and also have an empty $sitesArray, then your filtered in $filteredSites was too exclusive.
If you're still stuck, post an update.
I am currently scripting something that will go to a certain web page. Once on the web page it will click the run button which will calculate a value on the webpage and put it in a read only text area with the id="result Console". The code that does this is below:
$ie = New-Object -ComObject 'internetExplorer.Application'
$ie.Visible= $true
$ie.Navigate("https://example.com/example.aspx")
while ($ie.Busy -eq $true){Start-Sleep -seconds 1;}
$Link=$ie.Document.getElementsByTagName("input") | where-object {$_.className -eq "runButton"}
$Link.click()
After it does the above I need to get the text that is in the text area and save it to a txt file.
I was wondering if this was possible and if so how I would go about doing so?
I presume I could get the text from the text area in to a variable and then export this using the below command:
$example | out-file -filepath C:\temp\example.txt
I would appreciate any help you can give on this.
Thanks in advance,
SG
If the textarea has an id value set, I guess you can use the getElementById with the id as parameter:
$example = $ie.Document.getElementById("id of the textarea")
$example.value #should have the text you want to write to the txt file
It's been a while I wrote Powershell script but I remember doing it this way last time I needed the text from a textarea.
Hope this helps, good luck.
I make a report for data usage on my disk, I get info from all selected property like name, path, size... behalf one filedescription, for each scanned file this property is empty. For example when you select a file in windows Explorer and you select property in general tab you can see "Type of file", here for an Excel file the type of file is "Microsoft Excel Worksheet (.xlsx)".
gci c:\file | select *
How can i get this info?
I like to avoid external programs when I can, so I would suggestion using the registry.
$ext = ".xlsx"
$desc = (Get-ItemProperty "Registry::HKEY_Classes_root\$((Get-ItemProperty "Registry::HKEY_Classes_root\$ext")."(default)")")."(default)"
$desc
Microsoft Excel-regneark #Norwegian description
To use it with Select-Object you can modify it like this:
#You could define this inside Select-Object too, but it's a bit long so I extracted it first to clean up the code.
$extensiondesc = #{n="ExtensionDescription";e={ (Get-ItemProperty "Registry::HKEY_Classes_root\$((Get-ItemProperty "Registry::HKEY_Classes_root\$($_.Extension)")."(default)")")."(default)" }}
Get-ChildItem |
Select-Object Extension, $extensiondesc
Extension ExtensionDescription
--------- --------------------
.oxps XPS Document
.lnk Shortcut
.txt Text Document
Lets say $ext has the extension of the file.
For example -
$ext = ".bmp"
Following code will get you the description, if registered (you should add better error handling if appropriate for your scenario) -
$desc = (cmd /c assoc $ext).Split("=")[1]
$desc = (cmd /c assoc $desc).Split("=")[1]
Write-Host $desc
AFAIK, Powershell does not have any built in mechanism to get this information, and hence using cmd from powershell is the cheapest and easiest solution IMHO.
You could use the GetDetailsOf() method of the Shell.Application object:
$app = New-Object -COM 'Shell.Application'
$f = Get-Item 'C:\path\to\your\file'
$dir = $app.NameSpace($f.Directory.FullName)
$description = $dir.GetDetailsOf($dir.ParseName($f.Name), 2)
I have just improvised on Frode F's solution by adding a sorted list with unique entries to make it easier to read.
$extensiondesc = #{n="ExtensionDescription";
e={(Get-ItemProperty "Registry::HKEY_Classes_root\$((Get-ItemProperty "Registry::HKEY_Classes_root\$($_.Extension)")."(default)")")."(default)" }}
Get-ChildItem |
Select-Object -unique Extension, $extensiondesc |Sort-Object #{e="Extension";Ascending=$true},#{e="ExtensionDescription";Ascending=$false}
can someone please check this script and point me to the right direction. I'm not getting any errors though but script is not working as expected. I'm trying to achieve a shortcut target changed based on logged user.My powershell skills are basic and i'm sure must be missing some logic here:-
#$shell = new-object -com wscript.shell
$loguser="username"
$link ="test1.lnk"
$oldtarget=$link.tragetpath
$oldpath="c:\notepad.exe"
Get-ChildItem -Filter $link -Recurse
if ($oldtarget -eq $oldpath)
{
$csvfile=Import-csv "c:\test.csv"
$newtarget=$row.newpath
$user=$row.user
(get-Content $csvfile) | foreach-object {$_.$user -match $loguser} | -replace $oldtarget $newtarget
}
$link.SaveInfo
A way better way to modify shortcuts is to get rid of the VBScript shell and use P/Invoke. Take a look at sample code in Poshcode. It contains some 1200 lines of code, so I won't copy it here.
I am trying to write a script that automatically and silently moves a bunch of fonts into the Fonts special folder so they are available as if you had "installed" them from Explorer (by dragging and dropping, copying, or right-click and choosing Install). I have the Shell.Application part down all the way to the copy.
$FONTS = 0x14
$shell = New-Object -ComObject Shell.Application
$source = $shell.Namespace($downloaded_path)
$target = $shell.Namespace($FONTS)
$target.CopyHere($source.Items())
However, some systems may already have the fonts installed and I want the progress dialog to be hidden and any prompts to be silently accepted.
So, I'm investigating the Folder.CopyHere option flags.
4 Do not display a progress dialog box
16 Respond with "Yes to All" for any dialog box that is displayed.
I hope they are supported in this folder (some options are ignored by design). And I think these are in decimal, right? Do they need to be converted? However I pass them in, I still see both dialogs. I have tried
$options = 4 <-- don't expect int to work
$options = 0x4 <-- thought hexidecimal would be ok, the VB documentation shows &H4&
$options = "4" <-- string's the thing?
$options = [byte]4 <-- no luck with bytes
$options = [variant]4 <-- this isn't even a type accelerator!
And, if I can get one option working, how do I get both working? Do I bor them together? What about the formatting?
$options = 4 -bor 16
Or do I add them or convert them to hex?
$options = "{0:X}" -f (4 + 16)
You can use 4 -bor 16. It is hard to tell what this method expects since the type is VARIANT. I would have thought that it would take an integer value. If that doesn't work, this comment from the MSDN topic on Folder.CopyHere implies that a string should work:
function CopyFileProgress
{
param( $Source, $DstFolder, $CopyType = 0 )
# Convert the decimal to hex
$copyFlag = [String]::Format("{0:x}", $CopyType)
$objShell = New-Object -ComObject "Shell.Application"
$objFolder = $objShell.NameSpace($DestLocation)
$objFolder.CopyHere($Source, $copyFlag)
}
Although I wonder if the format string should be "0x{0:x}"?
Just be aware that for normal .NET flags style enums, you can pass multiple flags to a .NET (or command parameter) that is strongly typed to the enum like so:
$srv.ReplicationServer.Script('Creation,SomeOtherValue')
Oisin has written up some info on this subject in this blog post.
I had the same problem and found this in another thread, Worked perfectly for me.
If you want it to overwrite AND be silent change 0x10 to 0x14 (docs).
$destinationFolder.CopyHere($zipPackage.Items(), 0x14)
The Folder.CopyHere option flags may simply not work. This makes me sad. I'll have to investigate one of these other methods, all of which leave me in a bit of a bind.
Separate Process
Invoke the copy in a new process and hide the window using the ProcessStartInfo properties. I haven't implemented this yet, but I wonder if it will address the user-prompting for overwriting existing files?
Dim iProcess As New System.Diagnostics.ProcessStartInfo(AppDomain.CurrentDomain.BaseDirectory + “unzip.exe”)
iProcess.CreateNoWindow = True
Dim sArgs As String = ZippedFile
iProcess.Arguments = sArgs
iProcess.WindowStyle = ProcessWindowStyle.Hidden
Dim p As New System.Diagnostics.Process
iProcess.UseShellExecute = False
p = System.Diagnostics.Process.Start(iProcess)
p.WaitForExit(30000)
Dim s As Integer = p.ExitCode
iProcess.UseShellExecute = True
p.Dispose()
iProcess = Nothing
For Loop
Only copy non-existing items. This seems to fall down when I actually want to update an existing font with a new font file of the same name.
foreach($File in $Fontdir) {
$fontName = $File.Name.Replace(".ttf", " Regular")
$objFolderItem = $objFolder.ParseName($fontName);
if (!$objFolderItem) {
$objFolder.CopyHere($File.fullname,0x14)
}
}
Remove Existing
I'm thinking of removing all fonts of the same name as the ones I'm copying, then copying the set. Although that's kind of brutal. And I believe that there's another prompt if that font cannot be deleted because it's in use. sigh
The copy flags don't work for me. I setup a job in the install fonts script that detects the "Installing Fonts" window and send {Enter} to it so I am not overwriting existing fonts.
Start-Job –Name DetectAndClosePrompt –Scriptblock {
$i=1
[void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("'Microsoft.VisualBasic")
while ($i -eq 1) {
$windowPrompt = Get-Process -ErrorAction SilentlyContinue |? {$_.MainWindowTitle -like "*Installing Fonts*"}
[Microsoft.VisualBasic.Interaction]::AppActivate($windowPrompt.ID)
[System.Windows.Forms.SendKeys]::SendWait("{Enter}")
sleep 2
}
}
After all fonts are copied/installed... I remove the job, by name.
Get-Job DetectAndClosePrompt | Remove-Job -Force
That works for me on Windows 7, 8.x, & 10.
I'm seeing a number of Unzip folder operations, but really no one writing a solution to fit the Fonts folder situation. So I wrote my own! As it turns out, the Fonts folder does implement the Shell.Folder.CopyHere method, but does not honor any overloads passed for the second argument of the method. Why? Who knows! I suspect Raymond Chen of 'The Old new Thing' Windows Developer blog could explain it, but I don't know the answer. So we need instead to intelligently look for our fonts before trying to copy them, or we'll get a nasty message.
In my code, we check to see a font exists or not by checking for a match on the first four characters of the font name with a wildcard search. If the font doesn't exist, we assume this is the first time we're installing fonts on this system and set a special flag called $FirstInstall.
From then on in the script, if $FirstInstall is true, we install every font in the source font directory. On subsequent executions, we check to see if each font is a match, and if so, we abort that copy. If not, we go ahead and copy. This seems to work for most of my clients, thus far.
Here you go!
<#
.SYNOPSIS
Script to quietly handle the installation of fonts from a network source to a system
.DESCRIPTION
We Can't just move files into the %windir%\Fonts directory with a script, as a simple copy paste from command line doesn't trigger windows to note the new font
If we used that approach, the files would exist within the directory, but the font files woudln't be registered in windows, nor would applications
display the new font for use. Instead, we can make a new object of the Shell.Application type (effectively an invisible Windows Explorer Windows) and use its Copy method
Which is the functional equivalent of dragging an dropping font files into the Font folder, which does trigger the font to be installed the same as if you right clicked the font
and choose install.
.PARAMETER FontPath
The path of a folder where fonts reside on the network
.EXAMPLE
.\Install-Fonts.ps1 -FontPath "\\corp\fileshare\Scripts\Fonts"
Installing font...C:\temp\Noto\NotoSans-Bold.ttf
Installing font...C:\temp\Noto\NotoSans-BoldItalic.ttf
Installing font...C:\temp\Noto\NotoSans-Italic.ttf
Installing font...C:\temp\Noto\NotoSans-Regular.ttf
In this case, the fonts are copied from the network down to the system and installed silently, minus the logging seen here
import files needed for step 1, step 2, and step 5 of the migration process.
.EXAMPLE
.\Install-Fonts.ps1 -FontPath "\\corp\fileshare\Scripts\Fonts"
Font already exists, skipping
Font already exists, skipping
Font already exists, skipping
Font already exists, skipping
In this case, the fonts already existed on the system. Rather than display an annoying 'Overwrite font' dialog, we simply abort the copy and try the next file
.INPUTS
String.
.OUTPUTS
Console output
.NOTES
CREATED: 06/11/2015
Author: sowen#ivision.com
MODIFIED:06/11/2015
Author: sowen#ivision.com -Reserved...
#>
param
(
[Parameter(Mandatory)][string]$FontPath="C:\temp\Noto"
)
#0x14 is a special system folder pointer to the path where fonts live, and is needed below.
$FONTS = 0x14
#Make a refrence to Shell.Application
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.Namespace($FONTS)
ForEach ($font in (dir $fontsPath -Recurse -Include *.ttf,*.otf)){
#check for existing font (to suppress annoying 'do you want to overwrite' dialog box
if ((($objShell.NameSpace($FONTS).Items() | where Name -like "$($font.BaseName.Split('-')[0].substring(0,4))*") | measure).Count -eq 0){
$firstInstall = $true}
if ($firstInstall -ne $true) {Write-Output "Font already exists, skipping"}
else{
$objFolder.CopyHere($font.FullName)
Write-Output "Installing font...$($font.FullName)"
$firstInstall = $true
}
}
.\Install-Fonts.ps1 -FontPath "\\corp\fileshare\Scripts\Fonts"
There are several issues with #FoxDeploy's answer which is why it is not working. First issue is that you also want to check Fonts folder in %USERPROFILE% or you would get confirmation dialog. Second issue is that you want to avoid assuming '-' in font name.
Below is the fixed version that installs fonts from CodeFonts repo as an example:
$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.Drawing
# Clone chrissimpkins/codeface from which we will install fonts
if (!(Test-Path /GitHubSrc/codeface)){
git clone git://github.com/chrissimpkins/codeface.git /GitHubSrc/codeface
}
#0x14 is a special system folder pointer to the path where fonts live, and is needed below.
$FONTS = 0x14
$fontCollection = new-object System.Drawing.Text.PrivateFontCollection
#Make a refrence to Shell.Application
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.Namespace($FONTS)
# local path
$localSysPath = "$Env:USERPROFILE\AppData\Local\Microsoft\Windows\Fonts"
$localSysFonts = Get-ChildItem -Path $localSysPath -Recurse -File -Name | ForEach-Object -Process {[System.IO.Path]::GetFileNameWithoutExtension($_)}
$fontsPath="\GitHubSrc\codeface\fonts"
ForEach ($font in (dir $fontsPath -Recurse -Include *.ttf,*.otf)){
if ($localSysFonts -like $font.BaseName) {
Write-Output "SKIP: Font ${font} already exists in ${localSysPath}"
}
else {
$fontCollection.AddFontFile($font.FullName)
$fontName = $fontCollection.Families[-1].Name
#check for existing font (to suppress annoying 'do you want to overwrite' dialog box
if ((($objShell.NameSpace($FONTS).Items() | where Name -ieq $fontName) | measure).Count -eq 0){
Write-Output "INST: Font ${font}"
$objFolder.CopyHere($font.FullName)
$firstInstall = $true
}
else {
Write-Output "SKIP: Font ${font} already exists in SYSTEM FONTS"
}
}
# Read-Host -Prompt "Press Enter to continue"
}
You can just take a sum of your options. I was need to run CopyHere with two options - SILENT and NOCONFIRMATION. Look at the sample below:
function Unzip-Archive($targetpath, $destination)
{
$shell_app=new-object -com shell.application
$FOF_SILENT_FLAG = 4
$FOF_NOCONFIRMATION_FLAG = 16
$zip_file = $shell_app.namespace("$targetpath")
#Set the destination directory for the extracts
$destination = $shell_app.namespace("$destination")
#unzip the files
$destination.Copyhere($zip_file.items(), $FOF_SILENT_FLAG + $FOF_NOCONFIRMATION_FLAG)
}
I just got this to work by simply using + i.e.
function Expand-ZIPFile($file, $destination)
{
$shell = new-object -com shell.application
$zip = $shell.NameSpace($file)
foreach($item in $zip.items())
{
$shell.Namespace($destination).copyhere($item, 16+1024)
}
}