function contactOU
{
#This selects which OU to place the contact in.
write-host
write-host '~Contact type~'
write-host '1. Admin'
write-host '2. Assistant Owner'
write-host '3. Owner Partner'
write-host '4. Owner'
write-host '5. Team Leader'
write-host
$contacttype = (Read-host -prompt 'Which type of contact')
if($contacttype = "1") {$contactOU = "OU=Admins,OU=Marketing Companies,OU=Contacts,DC=company,DC=com,DC=au"}
if($contacttype = "2"){$contactOU = "OU=Assistant Owners,OU=Marketing Companies,OU=Contacts,DC=company,DC=com,DC=au"}
if($contacttype = "3"){$contactOU = "OU=Owner Partner,OU=Marketing Companies,OU=Contacts,DC=company,DC=com,DC=au"}
if($contacttype = "4"){$contactOU = "OU=Owners,OU=Marketing Companies,OU=Contacts,DC=company,DC=com,DC=au"}
if($contacttype = "5"){$contactOU = "OU=Team Leaders,OU=Marketing Companies,OU=Contacts,DC=company,DC=com,DC=au"}
else{write-host 'Please select a valid number',contactOU}
#For testing
write-host $contactOU
#May put this in an individual function?
New-ADObject -name $contactname -type Contact -Path $contactOU -OtherAttributes #{displayName=$contactname;mail=$emailaddress;targetAddress=$targetaddress}
}
The issue I'm having is, regardless of what number I select the IF statments choose the last option? (Team Leader OU). Does anyone know what is wrong with my IF statements?
Use -eq in your if statements.
if($contacttype -eq "1")
See about_Comparison_Operators
Kory Gill already found the problem within your if statements. However, consider to use PowerShell function parameters. Example:
function Get-ContactOu
{
Param(
[Parameter(Mandatory=$false, Position=0, ParameterSetName='Admin')]
[switch]$Admin,
[Parameter(Mandatory=$false, Position=0, ParameterSetName='AssistantOwner')]
[switch]$AssistantOwner,
[Parameter(Mandatory=$false, Position=0, ParameterSetName='OwnerPartner')]
[switch]$OwnerPartner,
[Parameter(Mandatory=$false, Position=0, ParameterSetName='Owner')]
[switch]$Owner,
[Parameter(Mandatory=$false, Position=0, ParameterSetName='TeamLeader')]
[switch]$TeamLeader
)
$ou = '';
if ($Admin) { $ou = 'Admins' }
if ($AssistantOwner) { $ou = 'Assistant Owners' }
if ($OwnerPartner) { $ou = 'Owner Partner' }
if ($Owner) { $ou = 'Owners' }
if ($TeamLeader) { $ou = 'Team Leaders' }
$path = 'OU={0},OU=Marketing Companies,OU=Contacts,DC=company,DC=com,DC=au' -f $ou
New-ADObject -name $contactname -type Contact -Path $path -OtherAttributes #{displayName=$contactname;mail=$emailaddress;targetAddress=$targetaddress}
}
Now you can use the function with a switch:
Get-ContactOu -Admin
Related
First off I would like to thank everyone for helping me work thru my issue.
Scope:
I am looking to write a script that will dynamically build the full set of permissions for each printer. As each printer has it's own Dynamic Group and is not allowed to have the everyone group applied to the printer.
Example:
Printer Name: PrinterA
AdGroup for Printer: gprt_PrinterA
Other groups assigned full (Print/Manage Doc/Manage Printer) permissions to the printer : Local Admin/Local Power User/Local Print Operator/Network Admins (Domain Group)
Other groups with Manage Documents and Print permissions to the printer: Endpoint (Domain Group)/Service Desk (Domain Group)/gprt_PrinterA (Domain Group)\
First what works and I see many examples about this across the web but does not meet my requirements:
$DefaultPrinterInfo = Get-Printer -Name PrinterA -Full
Set-Printer -Name PrinterB -PermissionSDDL ($DefaultPrinterInfo.PermissionSDDL)
IMPORTANT:
This however does not work to meet the required specifications. The reason is the gprt_PrinterA group can not exist on PrinterB. PrinterB must have the gprt_PrinterB Group.
In one example I have attempted to:
Set-Printer -Name PrinterB -PermissionSDDL "G:SYD:(A;;LCSWSDRCWDWO;;;BA)(A;OIIO;RPWPSDRCWDWO;;;BA)"
I have attempted to even dynamically create the default permission groups required and if this worked then it would be easy for me to just add 1 more group that is dynamically assigned:
(A;;LCSWSDRCWDWO;;;BA)(A;OIIO;RPWPSDRCWDWO;;;BA)
(A;;LCSWSDRCWDWO;;;PU)(A;OIIO;RPWPSDRCWDWO;;;PU)
(A;;LCSWSDRCWDWO;;;PO)(A;OIIO;RPWPSDRCWDWO;;;PO)
(A;;LCSWSDRCWDWO;;;S-1-5-21-51083937-621610274-1850952788-69794)(A;OIIO;RPWPSDRCWDWO;;;S-1-5-21-51083937-621610274-1850952788-69794)
(A;CIIO;RC;;;S-1-5-21-51083937-621610274-1850952788-69792)(A;OIIO;RPWPSDRCWDWO;;;S-1-5-21-51083937-621610274-1850952788-69792)(A;;SWRC;;;S-1-5-21-51083937-621610274-1850952788-69792)
(A;CIIO;RC;;;S-1-5-21-51083937-621610274-1850952788-69791)(A;OIIO;RPWPSDRCWDWO;;;S-1-5-21-51083937-621610274-1850952788-69791)(A;;SWRC;;;S-1-5-21-51083937-621610274-1850952788-69791)
I kept the groups clean for easy reading but essentially just make it a continuous line with "G:SYD:" in the beginning. Then replace the PermissionSDDL in the above powershell statement. Either way though, I keep getting the error: "[Set-Printer : Access was denied to the specific resource]"
I have even attempted to do the following:
SetSecurityDescriptor method of the Win32_Printer class
Set-PrinterPermission.ps1
The Security Descriptor Definition Language of Love (Part 2)
Adding Multiple Permissions to a Share
These did put me on the correct path! It lets me replace the permission on the printer. But it strips all existing permission, putting on only the single permission specified for the printer. I need to apply a whole set of permissions to the printer as you see above. I am a little out of my realm but learning how to build a Multi-ACL Package to apply to the printer.
I am ok with replacing all permissions, if I can assign a whole set of permissions, or simply add and remove to the existing permissions if they do or not exist.
What I have learned in my research the permission sets need to be:
Print/Manage this Printer
# G:SYD:(A;;LCSWSDRCWDWO;;;$SID)
Print
# G:SYD:(A;;SWRC;;;$SID)
Print/Manage this Printer/Manage Documents/Special Permissions
# G:SYD:(A;;LCSWSDRCWDWO;;;$SID)(A;OIIO;RPWPSDRCWDWO;;;$SID)
I hope someone the help me figure out a solution please.
Ok so after extensively researching I am getting closer.
The "Set-PrinterPermission" script is on the correct path. What I have had to do, is stripped out the ACE function from the script to place it into it's own function.
function New-PrinterACE
{
##[CmdletBinding(SupportsShouldProcess)]
Param (
[Parameter(
Mandatory = $true,
HelpMessage = "User/group to grant permissions"
)]
[String]$UserName,
[Parameter(
Mandatory = $true,
HelpMessage = "Permissions to apply"
)]
[ValidateSet('Takeownership', 'ReadPermissions', 'ChangePermissions', 'ManageDocuments', 'ManagePrinters', 'Print + ReadPermissions')]
[String]$Permission,
[Parameter(
Mandatory = $true,
HelpMessage = "Permissions to apply"
)]
[ValidateSet('Allow', 'Deny', 'System Audit')]
[String]$AccessType
)
$Ace = ([WMIClass] "Win32_Ace").CreateInstance()
$Trustee = ([WMIClass] "Win32_Trustee").CreateInstance()
Write-Verbose "Translating UserName (user or group) to SID"
$SID = (New-Object security.principal.ntaccount $UserName).translate([security.principal.securityidentifier])
Write-Verbose "Get binary form from SID and byte Array"
[byte[]]$SIDArray = , 0 * $SID.BinaryLength
$SID.GetBinaryForm($SIDArray, 0)
Write-Verbose "Fill Trustee object parameters"
$Trustee.Name = $UserName
$Trustee.SID = $SIDArray
Write-Verbose "Translating $Permission to the corresponding Access Mask"
Write-Verbose "Based on https://learn.microsoft.com/en-US/windows/win32/cimwin32prov/setsecuritydescriptor-method-in-class-win32-printer?redirectedfrom=MSDN"
Write-Verbose "https://social.technet.microsoft.com/Forums/Windows/en-US/a67e3ffd-5e41-4e2f-b1b9-c7c2f29a3a12/adding-permissions-to-an-existing-share"
switch ($Permission)
{
'Takeownership'
{
$Ace.AccessMask = "524288"
}
'ReadPermissions'
{
$Ace.AccessMask = "131072"
}
'ChangePermissions'
{
$Ace.AccessMask = "262144"
}
'ManageDocuments'
{
$Ace.AccessMask = "983088"
}
'ManagePrinters'
{
$Ace.AccessMask = "983052"
}
'Print + ReadPermissions'
{
$Ace.AccessMask = "131080"
}
}
Write-Verbose "Translating $AccessType to the corresponding numeric value"
Write-Verbose "Based on https://learn.microsoft.com/en-US/windows/win32/cimwin32prov/setsecuritydescriptor-method-in-class-win32-printer?redirectedfrom=MSDN"
switch ($AccessType)
{
"Allow"
{
$Ace.AceType = 0
$Ace.AceFlags = 0
}
"Deny"
{
$Ace.AceType = 1
$Ace.AceFlags = 1
}
"System Audit"
{
$Ace.AceType = 2
$Ace.AceFlags = 2
}
}
Write-Verbose "Write Win32_Trustee object to Win32_Ace Trustee property"
$Ace.Trustee = $Trustee
Return $ACE
}
$MyPrinterAces = #()
$MyPrinterAces += New-PrinterACE -UserName <DomainUserA> -Permission ManagePrinters -AccessType Allow
$MyPrinterAces += New-PrinterACE -UserName <DomainUserA> -Permission ManageDocuments -AccessType Allow
$MyPrinterAces += New-PrinterACE -UserName "DomainGroupA" -Permission ManageDocuments -AccessType Allow
$MyPrinterAces += New-PrinterACE -UserName "DomainGroupA" -Permission 'Print + ReadPermissions' -AccessType Allow
#https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmi-security-descriptor-objects#example-checking-who-has-access-to-printers
#https://stackoverflow.com/questions/60261292/explicit-access-array-from-acl-win32-api
This, with a few other cosmetic modifications to the "Set-PrinterPermission" script to accommodate; So that it now references this function to build the ACE's it uses and to add the ability for it to accommodate an array of multiple users/groups with permissions types.
function Set-PrinterPermission
{
[CmdletBinding(SupportsShouldProcess)]
Param (
[Parameter(
Mandatory = $true,
HelpMessage = "Server or array of servers",
ParameterSetName = 'OnePrinter'
)]
[Parameter(
Mandatory = $true,
HelpMessage = "Server or array of servers",
ParameterSetName = 'AllPrinters'
)]
[string[]]$Servers,
[Parameter(
HelpMessage = "Name of the Printer",
ParameterSetName = 'OnePrinter'
)]
[String]$PrinterName,
$PrinterPermissions =
#(
#('Administrators', 'ManagePrinters','Allow'),
#('Power Users', 'ManagePrinters','Allow'),
#('Print Operators', 'ManagePrinters','Allow'),
#('OHD – Network Support Team', 'ManagePrinters','Allow'),
#("OHD – PC Support Team", 'Print + ReadPermissions','Allow'),
#("OHD - Service Desk Users", 'Print + ReadPermissions','Allow')
)
)
Begin
{
$greenCheck =
#{
Object = [Char]8730
ForegroundColor = 'Green'
NoNewLine = $true
}
ConvertFrom-SddlString -Sddl $printer.PermissionSDDL
#Write-Host "Status check... " -NoNewline
#Start-Sleep -Seconds 1
#Write-Host #greenCheck
#Write-Host " (Done)"
Write-Output "Beginning Treatment ..."
Write-Verbose "creating instances of necessary classes ..."
$SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance()
$Aces = #()
Foreach ($PrinterPermission in $PrinterPermissions)
{
$Aces += New-PrinterACE -UserName $PrinterPermission[0] -Permission $PrinterPermission[1] -AccessType $PrinterPermission[2]
}
Write-Verbose "Write Win32_Ace and Win32_Trustee objects to SecurityDescriptor object"
$SD.DACL = $Aces
Write-Verbose "Set SE_DACL_PRESENT control flag"
$SD.ControlFlags = 0x0004
}
process
{
try
{
If ($PSCmdlet.ParameterSetName -eq "OnePrinter")
{
ForEach ($Server in $Servers)
{
$Printer = Get-Printer -ComputerName $Server -Name $PrinterName -ErrorAction Stop
$PrinterName = $Printer.name
Write-Output "Beginning treatment of: $PrinterName On: $Server"
Write-Verbose "Get printer object"
<#
It seems that i can't use the Filter parameter using a var
$PrinterWMI = Get-WMIObject -Class WIN32_Printer -Filter "name = $PrinterName"
I've also noticed that I've haven't the same result using Get-CimInstance in particular with
$PrinterCIM.psbase.scope
However I'm sure that using Get-CiMInstance will be better, but i don't know how to proceed
then I'm using the following "Legacy" approach
https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/the-security-descriptor-definition-language-of-love-part-1/ba-p/395202
https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/the-security-descriptor-definition-language-of-love-part-2/ba-p/395258
http://docs.directechservices.com/index.php/category-blog-menu/319-the-security-descriptor-definition-language-of-love
https://learn.microsoft.com/en-us/windows/win32/secauthz/ace-strings?redirectedfrom=MSDN
https://learn.microsoft.com/en-us/windows/win32/secauthz/access-tokens
#>
#$PrinterWMI = (Get-WmiObject -Class WIN32_Printer | Where-Object -FilterScript { $_.Name -like "wilpa0p11" }).GetSecurityDescriptor().Descriptor.dacl
$PrinterWMI = Get-WmiObject -Class WIN32_Printer | Where-Object -FilterScript { $_.Name -like $PrinterName }
Write-Verbose "Enable SeSecurityPrivilege privilegies"
$PrinterWMI.psbase.Scope.Options.EnablePrivileges = $true
Write-Verbose "Invoke SetSecurityDescriptor method and write new ACE to specified"
$PrinterWMI.SetSecurityDescriptor($SD)
Write-Verbose "Treatment of $PrinterName : Completed"
}
} # end if OnePrinter Parameter Set
If ($PSCmdlet.ParameterSetName -eq "AllPrinters")
{
ForEach ($Server in $Servers)
{
$Printers = Get-Printer -ComputerName $Server | Where-Object { $_.Shared -eq $true } -ErrorAction Stop
ForEach ($Printer in $Printers)
{
$PrinterName = $Printer.name
Write-Output "Beginning treatment of : $PrinterName"
Write-Verbose "Get printer object"
<#
It seems that i can't use the Filter parameter using a var
$PrinterWMI = Get-WMIObject -Class WIN32_Printer -Filter "name = $PrinterName"
I've also noticed that I've haven't the same result using Get-CimInstance in particular with
$Printer.psbase.scope
then I'm using the following approach
However I'm sure that using Get-CiMInstance will be better
#>
$PrinterWMI = Get-WmiObject -Class WIN32_Printer | Where-Object -FilterScript { $_.Name -like $PrinterName }
Write-Verbose "Enable SeSecurityPrivilege privilegies"
$PrinterWMI.psbase.Scope.Options.EnablePrivileges = $true
Write-Verbose "Invoke SetSecurityDescriptor method and write new ACE to specified"
$PrinterWMI.SetSecurityDescriptor($SD)
Write-Output "Treatment of $PrinterName : Completed"
}
}
} # end if All Printers Parameter Set
} # End Try
catch
{
Write-Error "Hoops an error occured"
Write-Error $_.Exception.Message
}
}
end
{
Write-Output "All treatments : completed"
}
} # end function
Now this is working great I can easily add the dynamic group as a parameter and a ACE will get assigned to the security descriptor of the printer.
Now my problem is I am unable to add the "Manage Documents" permission to the printer. if anyone can help me with this I will have my project complete.
The permission is assigned correctly for Printing only, and Manage Printer.
Primary Issue needing help resolving:
I am so very close now... what am I doing wrong to apply the "Manage Documents" permission to the printer ACL?
The Image below is the results of the script trying to apply the "Manage Documents" Permissions.
Very Minor Cosmetic help:
is there a way to validate the $PrinterPermissions in the Parameters section of the code? My thinking is to validate the parameter in the begin section of the code and exit out if one of my validations fail. not sure if there is a better way.
I'm trying to write 2 functions:
the first one (Get-Lab) retrieves a [Lab] object
the second one (remove-Lab) remove a [Lab] object
[Lab] is a class defined in my module.
When a run Get-Lab I correctly retrieve my lab instance with the correct type :
When I run Remove-Lab -Lab (Get-Lab -Name Mylab), the operation is correctly performed:
But when I try to pass the [Lab] object through the pipeline it fails.
The function does not receive the object through the pipeline. However I've set the -Lab Parameter as mandatory with ValueFromPipeline=$true.
Function Remove-Lab{
[CmdletBinding(DefaultParameterSetName='Lab')]
param (
[Parameter(ValueFromPipeline=$true,ParameterSetName='Lab',Position=0,Mandatory=$true)]
[Lab]
$Lab,
# Parameter help description
[Parameter(Position=1,Mandatory=$false)]
[switch]
$Force=$false
)
begin {
Write-host ("`tLabName : {0}" -f $Lab.Name) -ForegroundColor Yellow
if ($null -ne $Lab) {
$LabToRemove = $Lab
}
if (-not [string]::IsNullOrEmpty($LabId)) {
$LabToRemove = Get-Lab -Id $LabId
}
if (-not [string]::IsNullOrEmpty($Name)) {
$LabToRemove = Get-Lab -Name $Name
}
if ($null -eq $LabToRemove) {
throw "There is no Lab with specified characteristics. Please check your input"
}
}
process {
$DoRemoval = $true
if ($Force.IsPresent -eq $false) {
while ($null -eq $UserInput -or $UserInput -notin #('Y','N')) {
$UserInput = Read-HostDefault -Prompt "Are you sure want to remove the selected Lab and all its components ? [Y]es, [N]o" -Default 'N'
if ($UserInput -eq 'N') {
$DoRemoval = $false
}
}
Write-Host ("`tUser Input : {0}" -f $UserInput) -ForegroundColor Green
}
if ($DoRemoval -eq $true) {
Write-Host ("`tAbout to Remove the following Lab : {0}" -f $LabToRemove.Name) -ForegroundColor Green
}
}
end {
}
}
As you can see below when a debug this function, the $Lab Parameter is null.
Do you have any idea about this issue ?
Since the function is testing on $LabId or $Name, these variables need to exist in the function and at the moment they do not.
Try changing the parameters to:
[CmdletBinding(DefaultParameterSetName='LabId')]
param (
[Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName = $true, ParameterSetName='LabId',Position=0,Mandatory=$true)]
[string]$LabId,
[Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName = $true, ParameterSetName='LabName',Position=0,Mandatory=$true)]
[string]$Name,
# Parameter help description
[switch]$Force # no need to set a switch to $false because if you don't send that param, the undelying value will be $false by default
)
Then remove
Write-host ("`tLabName : {0}" -f $Lab.Name) -ForegroundColor Yellow
if ($null -ne $Lab) {
$LabToRemove = $Lab
}
Important part here is the ValueFromPipelineByPropertyName = $true declaration
begin runs before anything else, including pipeline parameter binding - so you need to move code that inspects a pipeline-bound parameter (like $Lab) to the process block:
Function Remove-Lab{
[CmdletBinding(DefaultParameterSetName='Lab')]
param (
[Parameter(ValueFromPipeline=$true,ParameterSetName='Lab',Position=0,Mandatory=$true)]
[Lab]
$Lab,
# Parameter help description
[Parameter(Position=1,Mandatory=$false)]
[switch]
$Force=$false
)
process {
Write-host ("`tLabName : {0}" -f $Lab.Name) -ForegroundColor Yellow
if ($null -ne $Lab) {
$LabToRemove = $Lab
}
if (-not [string]::IsNullOrEmpty($LabId)) {
$LabToRemove = Get-Lab -Id $LabId
}
if (-not [string]::IsNullOrEmpty($Name)) {
$LabToRemove = Get-Lab -Name $Name
}
if ($null -eq $LabToRemove) {
throw "There is no Lab with specified characteristics. Please check your input"
}
$DoRemoval = $true
if ($Force.IsPresent -eq $false) {
while ($null -eq $UserInput -or $UserInput -notin #('Y','N')) {
$UserInput = Read-HostDefault -Prompt "Are you sure want to remove the selected Lab and all its components ? [Y]es, [N]o" -Default 'N'
if ($UserInput -eq 'N') {
$DoRemoval = $false
}
}
Write-Host ("`tUser Input : {0}" -f $UserInput) -ForegroundColor Green
}
if ($DoRemoval -eq $true) {
Write-Host ("`tAbout to Remove the following Lab : {0}" -f $LabToRemove.Name) -ForegroundColor Green
}
}
i have a script that works fine for exchange on-prem and online with basic auth.
The problem is that my organization now accetps only oAuth metod for echangeonline.
This script is for insert a quickaction in the user logging with in his mailbox.
How i have to change the connect method to work with oauth within exchangeonline?
i'll have the same commandelets after that?
Begin
{
Import-Module Activedirectory
}
Process
{
$curUser = $env:USERNAME
$curDomain = $env:USERDNSDOMAIN
$aUser = Get-ADUser -Identity "${curUser}" -Server "${curDomain}" -Properties "EmailAddress"
if ([string]::IsNullOrEmpty($aUser.EmailAddress)) {
throw "User ${curUser} has no Emailaddress"
}
$aUser.EmailAddress
"$($aUser.GivenName) $($aUser.Surname)"
}
}
function Connect-Exchange{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName
)
try {
Connect-Exchange365 -MailboxName $MailboxName
} catch {
Connect-ExchangeONPREM -MailboxName $MailboxName
}
}
function Connect-Exchange365
{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName
)
Begin
{
############## NEW CODE HERE WITH OAUTH ################
$service = "?????????????????"
if (!$service.URL) {
throw "Error connecting to EWS"
}
else
{
return $service
}
}
}
function Connect-ExchangeONPREM
{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName
)
Begin
{
## Load Managed API dll
###CHECK FOR EWS MANAGED API, IF PRESENT IMPORT THE HIGHEST VERSION EWS DLL, ELSE EXIT
$EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll")
if (Test-Path $EWSDLL)
{
Import-Module $EWSDLL
}
else
{
"$(get-date -format yyyyMMddHHmmss):"
"This script requires the EWS Managed API 1.2 or later."
"Please download and install the current version of the EWS Managed API from"
"http://go.microsoft.com/fwlink/?LinkId=255472"
""
"Exiting Script."
exit
}
## Set Exchange Version
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2
## Create Exchange Service Object
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)
## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials
#Credentials Option 1 using UPN for the windows Account
#$psCred = Get-Credential
############ $creds = New-Object System.Net.NetworkCredential($Credentials.UserName.ToString(),$Credentials.GetNetworkCredential().password.ToString())
############ $service.Credentials = $creds
#Credentials Option 2
$service.UseDefaultCredentials = $true
$service.TraceEnabled = $true
## Choose to ignore any SSL Warning issues caused by Self Signed Certificates
## Code From http://poshcode.org/624
## Create a compilation environment
$Provider=New-Object Microsoft.CSharp.CSharpCodeProvider
$Compiler=$Provider.CreateCompiler()
$Params=New-Object System.CodeDom.Compiler.CompilerParameters
$Params.GenerateExecutable=$False
$Params.GenerateInMemory=$True
$Params.IncludeDebugInformation=$False
$Params.ReferencedAssemblies.Add("System.DLL") | Out-Null
$TASource=#'
namespace Local.ToolkitExtensions.Net.CertificatePolicy{
public class TrustAll : System.Net.ICertificatePolicy {
public TrustAll() {
}
public bool CheckValidationResult(System.Net.ServicePoint sp,
System.Security.Cryptography.X509Certificates.X509Certificate cert,
System.Net.WebRequest req, int problem) {
return true;
}
}
}
'#
$TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
$TAAssembly=$TAResults.CompiledAssembly
## We now create an instance of the TrustAll and attach it to the ServicePointManager
$TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
[System.Net.ServicePointManager]::CertificatePolicy=$TrustAll
## end code from http://poshcode.org/624
## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use
# CAS URL Option 1 Autodiscover
$service.AutodiscoverUrl($MailboxName,{$true})
Write-host ("Using CAS Server : " + $Service.url)
## Optional section for Exchange Impersonation
#$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
if(!$service.URL){
throw "Error connecting to EWS"
}
else
{
return $service
}
}
}
function ConvertFolderid{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$hexid,
[Parameter(Position=1, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service,
[Parameter(Position=2, Mandatory=$true)] [string]$MailboxName
)
Begin
{
$aiItem = New-Object Microsoft.Exchange.WebServices.Data.AlternateId
$aiItem.Mailbox = $MailboxName
$aiItem.UniqueId = $hexId
$aiItem.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::HexEntryId;
return $global:service.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::EWSId)
}
}
#######################
<#
.SYNOPSIS
Gets the QuickSteps folder in a Mailbox using the Exchange Web Services API
.DESCRIPTION
Gets the QuickSteps folder in a Mailbox using the Exchange Web Services API
Requires the EWS Managed API from https://www.microsoft.com/en-us/download/details.aspx?id=42951
.EXAMPLE
Example 1 To Gets the QuickSteps folder in a Mailbox using the Exchange Web Services API
Get-QuickStepsFolder -MailboxName mailbox#domain.com
#>
########################
function Get-QuickStepsFolder
{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$false)] [Microsoft.Exchange.WebServices.Data.ExchangeService]$service
)
Begin
{
#if(!$service){
$localservice = Connect-Exchange -MailboxName $MailboxName
#}
$PidTagAdditionalRenEntryIdsEx = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x36D9, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropset.Add($PidTagAdditionalRenEntryIdsEx)
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$MailboxName)
try {
$IPM_ROOT = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($localservice,$folderid,$psPropset)
} catch {
$localservice = Connect-ExchangeONPREM -MailboxName $MailboxName
$IPM_ROOT = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($localservice,$folderid,$psPropset)
}
$global:service = $localservice
$binVal = $null;
$AdditionalRenEntryIdsExCol = #{}
if($IPM_ROOT.TryGetProperty($PidTagAdditionalRenEntryIdsEx,[ref]$binVal)){
$hexVal = [System.BitConverter]::ToString($binVal).Replace("-","");
##Parse Binary Value first word is Value type Second word is the Length of the Entry
$Sval = 0;
while(($Sval+8) -lt $hexVal.Length){
$PtypeVal = $hexVal.SubString($Sval,4)
$PtypeVal = $PtypeVal.SubString(2,2) + $PtypeVal.SubString(0,2)
$Sval +=12;
$PropLengthVal = $hexVal.SubString($Sval,4)
$PropLengthVal = $PropLengthVal.SubString(2,2) + $PropLengthVal.SubString(0,2)
$PropLength = [Convert]::ToInt64($PropLengthVal, 16)
$Sval +=4;
$ProdIdEntry = $hexVal.SubString($Sval,($PropLength*2))
$Sval += ($PropLength*2)
#$PtypeVal + " : " + $ProdIdEntry
$AdditionalRenEntryIdsExCol.Add($PtypeVal,$ProdIdEntry)
}
}
$QuickStepsFolder = $null
if($AdditionalRenEntryIdsExCol.ContainsKey("8007")){
$siId = ConvertFolderid -service $service -MailboxName $MailboxName -hexid $AdditionalRenEntryIdsExCol["8007"]
$QuickStepsFolderId = new-object Microsoft.Exchange.WebServices.Data.FolderId($siId.UniqueId.ToString())
$QuickStepsFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($global:service,$QuickStepsFolderId)
}
else{
Write-Host ("QuickSteps folder not found")
throw ("QuickSteps folder not found")
}
write-host "end of Get-QuickStepsFolder"
write-host " ---FLD --- $($QuickStepsFolder.DisplayName)"
return $QuickStepsFolder
}
}
function Get-ExistingStepNames{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.Folder]$QuickStepsFolder
)
Begin
{
$NameList = #{}
$enc = [system.Text.Encoding]::ASCII
$PR_ROAMING_XMLSTREAM = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x7C08,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropset.Add($PR_ROAMING_XMLSTREAM)
#Define ItemView to retrive just 1000 Items
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$ivItemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Associated
$fiItems = $null
do{
$fiItems = $QuickStepsFolder.FindItems($ivItemView)
if($fiItems.Items.Count -gt 0){
[Void]$global:service.LoadPropertiesForItems($fiItems,$psPropset)
foreach($Item in $fiItems.Items){
$propval = $null
if($Item.TryGetProperty($PR_ROAMING_XMLSTREAM,[ref]$propval)){
[XML]$xmlVal = $enc.GetString($propval)
if(!$NameList.ContainsKey($xmlVal.CombinedAction.Name.ToLower())){
$NameList.Add($xmlVal.CombinedAction.Name.Trim().ToLower(),$xmlVal)
}
}
}
}
$ivItemView.Offset += $fiItems.Items.Count
}while($fiItems.MoreAvailable -eq $true)
return $NameList
}
}
function Get-ExistingSteps{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [Microsoft.Exchange.WebServices.Data.Folder]$QuickStepsFolder
)
Begin
{
$NameList = #{}
$enc = [system.Text.Encoding]::ASCII
$PR_ROAMING_XMLSTREAM = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x7C08,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropset.Add($PR_ROAMING_XMLSTREAM)
#Define ItemView to retrive just 1000 Items
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$ivItemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Associated
$fiItems = $null
do{
$fiItems = $QuickStepsFolder.FindItems($ivItemView)
if($fiItems.Items.Count -gt 0){
[Void]$global:service.LoadPropertiesForItems($fiItems,$psPropset)
foreach($Item in $fiItems.Items){
$propval = $null
if($Item.TryGetProperty($PR_ROAMING_XMLSTREAM,[ref]$propval)){
[XML]$xmlVal = $enc.GetString($propval)
if(!$NameList.ContainsKey($xmlVal.CombinedAction.Name.ToLower())){
$NameList.Add($xmlVal.CombinedAction.Name.Trim().ToLower(),$Item)
}
}
}
}
$ivItemView.Offset += $fiItems.Items.Count
}while($fiItems.MoreAvailable -eq $true)
return $NameList
}
}
#######################
<#
.SYNOPSIS
Gets the existing Outlook Quick Steps from a Mailbox using the Exchange Web Services API
.DESCRIPTION
Gets the existing Outlook Quick Steps from a Mailbox using the Exchange Web Services API
Requires the EWS Managed API from https://www.microsoft.com/en-us/download/details.aspx?id=42951
.EXAMPLE
Example 1 To Gets the existing Outlook Quick Steps from a Mailbox using the Exchange Web Services API
Get-QuickSteps -MailboxName mailbox#domain.com
This returns a HashTable of the QuickSteps to access a Quickstep within the collection use the Index value eg
$QuickSteps = Get-QuickSteps -MailboxName mailbox#domain.com
$QuickSteps["clutter"]
#>
########################
function Get-QuickSteps{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName
)
Begin{
#Connect
#$service = Connect-Exchange -MailboxName $MailboxName -Credential $Credentials
$QuickStepsFolder = Get-QuickStepsFolder -MailboxName $MailboxName -service $global:service
$ExistingSteps = Get-ExistingStepNames -MailboxName $MailboxName -QuickStepsFolder $QuickStepsFolder
Write-Output $ExistingSteps
}
}
#######################
<#
.SYNOPSIS
Exports an Outlook Quick Step XML settings from a QuickStep Item in a Mailbox using the Exchange Web Services API
.DESCRIPTION
Exports an Outlook Quick Step XML settings from a QuickStep Item in a Mailbox using the Exchange Web Services API
Requires the EWS Managed API from https://www.microsoft.com/en-us/download/details.aspx?id=42951
.EXAMPLE
Example 1 Exports an Outlook Quick Step XML settings from a QuickStep Item in a Mailbox to a file
Export-QuickStepXML -MailboxName mailbox#domain -Name 'Name of QuickStep' -FileName c:\temp\exportFile.xml
#>
########################
function Export-QuickStepXML{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [string]$Name,
[Parameter(Position=2, Mandatory=$true)] [string]$FileName
)
Begin{
#Connect
#$service = Connect-Exchange -MailboxName $MailboxName -Credential $Credentials
$QuickStepsFolder = Get-QuickStepsFolder -MailboxName $MailboxName -service $global:service
$ExistingSteps = Get-ExistingSteps -MailboxName $MailboxName -QuickStepsFolder $QuickStepsFolder
if($ExistingSteps.ContainsKey($Name.Trim().ToLower())){
$PR_ROAMING_XMLSTREAM = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x7C08,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropset.Add($PR_ROAMING_XMLSTREAM)
$propval = $null
if($ExistingSteps[$Name.Trim().ToLower()].TryGetProperty($PR_ROAMING_XMLSTREAM,[ref]$propval)){
[System.IO.File]::WriteAllBytes($FileName,$propval)
Write-Host ('Exported to ' + $FileName)
}
}
}
}
function Create-QuickStepFromXML
{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [String]$XMLFileName
)
Begin
{
#Connect
[xml]$QuickStepXML = Get-Content -Path $XMLFileName
$DisplayName = $QuickStepXML.CombinedAction.Name
$Name = $QuickStepXML.CombinedAction.Name.ToLower()
$service = Connect-Exchange -MailboxName $MailboxName
$QuickStepsFolder = Get-QuickStepsFolder -MailboxName $MailboxName -service $service
$QuickStepItem = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage -ArgumentList $global:service
$QuickStepItem.ItemClass = "IPM.Microsoft.CustomAction"
$ExistingSteps = Get-ExistingStepNames -MailboxName $MailboxName -QuickStepsFolder $QuickStepsFolder
if(!$ExistingSteps.ContainsKey($Name.Trim().ToLower())){
$PR_ROAMING_XMLSTREAM = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x7C08,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
$enc = [system.Text.Encoding]::ASCII
$QuickStepItem.SetExtendedProperty($PR_ROAMING_XMLSTREAM,$enc.GetBytes((Get-Content -Path $XMLFileName)))
$QuickStepItem.IsAssociated = $true
$QuickStepItem.Save($QuickStepsFolder.Id)
Write-host
Write-host ("SUCCESS Created QuickStep " + $DisplayName)
}
else
{
Write-host
Write-host ("FAILED: QuickStep with name " + $DisplayName + " already exists")
# throw ("Step with Name " + $DisplayName + " already exists")
}
}
}
#######################
<#
.SYNOPSIS
Deletes an Outlook Quick Step from a Mailbox using the Exchange Web Services API
.DESCRIPTION
Deletes an Outlook Quick Step from a Mailbox using the Exchange Web Services API
Requires the EWS Managed API from https://www.microsoft.com/en-us/download/details.aspx?id=42951
.EXAMPLE
Example 1 To Delete an Outlook Quick Step from a Mailbox give the name of the Quickstep
Delete-QuickStep -MailboxName mailbox#domain -Name 'Name of QuickStep'
#>
########################
function Delete-QuickStep{
param(
[Parameter(Position=0, Mandatory=$true)] [string]$MailboxName,
[Parameter(Position=1, Mandatory=$true)] [String]$Name
)
Begin
{
#Connect
$service = Connect-Exchange -MailboxName $MailboxName
$QuickStepsFolder = Get-QuickStepsFolder -MailboxName $MailboxName -service $service
$ExistingSteps = Get-ExistingSteps -MailboxName $MailboxName -QuickStepsFolder $QuickStepsFolder
if($ExistingSteps.ContainsKey($Name.Trim().ToLower())){
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes",""
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No",""
$choices = [System.Management.Automation.Host.ChoiceDescription[]]($yes,$no)
$message = "Do you want to Delete QuickStep with Name " + $Name.Trim()
$result = $Host.UI.PromptForChoice($caption,$message,$choices,1)
if($result -eq 0) {
$ExistingSteps[$Name.Trim().ToLower()].Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
Write-Host ("QuickStep Deleted")
}
else{
Write-Host ("No Action Taken")
}
}
else{
Write-Host -ForegroundColor Yellow ("No QuickStep found")
}
}
}
########### MAIN #############
$XML_QuickStepsContent=#'
<?xml version="1.0"?>
<CombinedAction Ordinal="200" Tooltip="" Icon="FileSendAsAttachment" Name="Sign as SPAM" Version="154433">
<ActionForwardAsAttach>
<Subject>TEST - PLS IGNORE: <Subject></Subject>
<Location/>
<Body/>
<Send>1</Send>
<FlagDays>0</FlagDays>
<Recipient>
<EntryId>00000000DCA740C8C042101AB4B908002B2FE18201000000000000002F6F3D4D41494C2F6F753D45786368616E67652041646D696E6973747261746976652047726F7570202846594449424F484632335350444C54292F636E3D526563697069656E74732F636E3D4D62782047454152202847654F5320456D61696C20416E616C7973697320616E6420526573706F6E73652962616500</EntryId>
</Recipient>
</ActionForwardAsAttach>
<ActionMoveToFolder>
<Folder>800101012E000000A486FB38C9000000D286FB380000000020C51E61B74A4A44921273CE8E5927E30100293C04FDADC95F4FBF47717FB2F56B5B00000068F0D500000000000038A1BB1005E5101AA1BB08002B2A56C20000454D534D44422E444C4C00000000000000001B55FA20AA6611CD9BC800AA002FC45A0C00000033323136343731352D343937622D346665382D623535632D3731323539636665646264624065787465726E616C2E656E692E636F6D002F6F3D4D41494C2F6F753D45786368616E67652041646D696E6973747261746976652047726F7570202846594449424F484632335350444C54292F636E3D526563697069656E74732F636E3D434F323933353065333400</Folder>
</ActionMoveToFolder>
</CombinedAction>
'#
try {
$mailAdddress, $username = getCurrentUserMailboxAndName
$res = read-host "Create quick steps for ${username} mailbox ${mailAdddress}? [y|N]"
if ("y" -eq $res.toLower()) {
$tmpFile = New-TemporaryFile
Out-File -FilePath $tmpFile.FullName -InputObject $XML_QuickStepsContent
Create-QuickStepFromXML $mailAdddress -XMLFileName $tmpFile.FullName
Remove-Item $tmpFile
} else {
Write-Host
Write-Host "Nothing to do"
}
} catch {
Write-Host
Write-Host "ERROR: $($_.exception)"
}
Write-Host
$close = read-host "Press Enter to finish"
Write-Host
Write-Host "Script terminated" + $close ``
There are a few ways you could do it probably the easiest is to use the ADAL.dll's eg
Write-Verbose("Using Modern Auth")
if ([String]::IsNullOrEmpty($ClientId)) {
$ClientId = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
}
Import-Module ($script:ModuleRoot + "/bin/Microsoft.IdentityModel.Clients.ActiveDirectory.dll") -Force
$Context = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.microsoftonline.com/common")
if ($Credentials -eq $null) {
$PromptBehavior = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters -ArgumentList Auto
$token = ($Context.AcquireTokenAsync("https://outlook.office365.com", $ClientId , "urn:ietf:wg:oauth:2.0:oob", $PromptBehavior)).Result
$service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.OAuthCredentials($token.AccessToken)
}else{
$AADcredential = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserPasswordCredential" -ArgumentList $Credentials.UserName.ToString(), $Credentials.GetNetworkCredential().password.ToString()
$token = [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContextIntegratedAuthExtensions]::AcquireTokenAsync($Context,"https://outlook.office365.com",$ClientId,$AADcredential).result
$service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.OAuthCredentials($token.AccessToken)
}
A few other examples https://ingogegenwarth.wordpress.com/2018/08/02/ews-and-oauth/ and https://gsexdev.blogspot.com/2019/10/using-msal-microsoft-authentication.html
I've got this script that connects to Sharepoint Online, indexes all the files and folders, downloads them all in a systematic fashion and churns out a .csv with the name of file, folders, size, path, etc.
For various reasons I've ended up in a situation where I've got all the data, but the metadata is corrupted (the .csv file aforementioned).
Unfortunately re running the whole script just for that isn't really an option, as that would require around 90 hours.
I've been trying to break the code down in order to remove the "download files" functions and just keep the part that generates the .csv, but so far without luck.
I've found the Function that seem to be in charge of it (WriteLog), but I'm struggling to separate it from the rest.
P.S. The code is not mine, I've inherited it from a developer I haven't got access to (unfortunately)
Please find the code below:
param(
[Parameter(Mandatory = $true)]
[string]$srcUrl,
[Parameter(Mandatory = $true)]
[string]$username,
[Parameter(Mandatory = $false,HelpMessage = "From Date: (dd/mm/yyyy)")]
[string]$fromDate,
[Parameter(Mandatory = $false,HelpMessage = "To Date: (dd/mm/yyyy)")]
[string]$toDate,
[Parameter(Mandatory = $true)]
[string]$folderPath,
[Parameter(Mandatory = $true)]
[string]$csvPath
) #end param
cls
#Load SharePoint CSOM Assemblies
Add-Type -Path "C:\Program Files\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.SharePoint.Client.dll"
Add-Type -Path "C:\Program Files\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.SharePoint.Client.Runtime.dll"
$global:OutFilePath = -join ($csvPath,"\Documents.csv")
$global:OutFilePathError = -join ($csvPath,"\ErrorLog_GetDocuments.csv")
$header = "Title,Type,Parent,Name,Path,FileSize(bytes),Created,Created by,Modified,Modified by,Matterspace title,Matterspace url"
$srcLibrary = "Documents"
$securePassword = Read-Host -Prompt "Enter your password: " -AsSecureString
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials ($username,$securePassword)
$sUrl = [System.Uri]$srcUrl
$domainUrl = -join ("https://",$sUrl.Host)
function WriteLog
{
param(
[Parameter(Mandatory = $true)] $title,$type,$folderName,$name,$path,$fileSize,$created,$createdby,$modifed,$modifiedby,$matterspacetitle,$materspaceUrl
)
$nowTime = Get-Date -Format "dd-MMM-yy,HH:mm:ss"
$folderName = $folderName.Replace(",","|") ### sometime folder / file name has comma so replace it with something
$name = $name.Replace(",","|")
#$path = $path.Replace(",","|")
$title=[System.String]::Concat("""""""$title""""""")
$type=[System.String]::Concat("""""""$type""""""")
$folderName=[System.String]::Concat("""""""$folderName""""""")
$name=[System.String]::Concat("""""""$name""""""")
$path=[System.String]::Concat("""""""$path""""""")
$fileSize=[System.String]::Concat("""""""$fileSize""""""")
$created=[System.String]::Concat("""""""$created""""""")
$createdby=[System.String]::Concat("""""""$createdby""""""")
$modified=[System.String]::Concat("""""""$modified""""""")
$modifiedby=[System.String]::Concat("""""""$modifiedby""""""")
$matterspacetitle=[System.String]::Concat("""""""$matterspacetitle""""""")
$materspaceUrl=[System.String]::Concat("""""""$materspaceUrl""""""")
$lineContent = "$("$title"),$($type),$($folderName),$($name),$($path),$($fileSize),$($created),$($createdby),$($modified),$($modifiedby),$($matterspacetitle),$($materspaceUrl)"
Add-Content -Path $global:OutFilePath -Value "$lineContent"
}
#Function to get all files of a folder
Function Get-FilesFromFolder([Microsoft.SharePoint.Client.Folder]$Folder,$SubWeb,$MTitle)
{
Write-host -f Yellow "Processing Folder:"$Folder.ServerRelativeUrl
$folderItem = $Folder.ListItemAllFields
#$srcContext.Load($f)
$Ctx.Load($folderItem)
$Ctx.ExecuteQuery()
#Get All Files of the Folder
$Ctx.load($Folder.files)
$Ctx.ExecuteQuery()
$authorEmail = $folderItem["Author"].Title
$editorEmail = $folderItem["Editor"].Title
$filepath = $folderItem["FileDirRef"]
if([string]::IsNullOrEmpty($filepath))
{
$filepath=$Folder.ServerRelativeUrl
}
$created = $folderItem["Created"]
$modified = $folderItem["Modified"]
$title = $folderItem["Title"]
if ([string]::IsNullOrEmpty($title))
{
$title = "Not Specified"
}
#$fileSize = $fItem["File_x0020_Size"]
$fileName = $Folder.Name
#list all files in Folder
write-host $Folder.Name
$splitString=$Folder.ServerRelativeUrl -split('/')
$dirUrl="";
write-host $splitString.Length
$parentUrl=""
For($i=3; $i -le $splitString.Length;$i++)
{
if($splitString[$i] -notcontains('.'))
{
Write-Host $i
Write-Host $splitString[$i]
$dirUrl=-join($dirUrl,"\",$splitString[$i])
$parentUrl=-join($parentUrl,"\",$splitString[$i+1])
}
}
$dirPath = -join ($folderPath,$dirUrl)
WriteLog $title "Folder" $parentUrl.TrimEnd('\') $fileName $filepath 0 $created $authorEmail $modified $editorEmail $MTitle $SubWeb
write-host $dirPath
if (-not (Test-Path -Path $dirPath))
{
New-Item -ItemType directory -Path $dirPath
}
ForEach ($File in $Folder.files)
{
try{
$remarkDetail = ""
$replacedUser = ""
$fItem = $File.ListItemAllFields
#$srcContext.Load($f)
$Ctx.Load($fItem)
$Ctx.ExecuteQuery()
$authorEmail = $fItem["Author"].Email
$editorEmail = $fItem["Editor"].Email
$filepath = $fItem["FileDirRef"]
$fileSizeBytes = $fItem["File_x0020_Size"];
$fileSize = ($fileSizeBytes) / 1MB
$fileName = $fItem["FileLeafRef"]
$title = $fItem["Title"]
$filecreated = $fitem["Created"]
$fileModified = $fitem["Modified"]
$FileUrl = $fItem["FileRef"]
$Fname=$File.Name
if ([string]::IsNullOrEmpty($title))
{
$title = "Not Specified"
}
#$title,$type, $folderName,$name,$path,$fileSize,$created,$createdby,$modifed,$modifiedby,$matterspacetitle,$materspaceUrl
$dateToCompare = Get-Date (Get-Date -Date $fileModified -Format 'dd/MM/yyyy')
#Get the File Name or do something
if (($dateToCompare -ge $startDate -and $dateToCompare -le $endDate) -or ($startDate -eq $null -and $endDate -eq $null))
{
$downloadUrl = -join ($dirPath,$File.Name)
$fromfile = -join ($domainUrl,$FileUrl)
Write-Host "Downloading the file from " $fromfile -ForegroundColor Cyan
try{
$webclient = New-Object System.Net.WebClient
$webclient.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials ($username,$securePassword)
$webclient.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED","f")
$webclient.DownloadFile($fromfile,$downloadUrl)
$webclient.Dispose()
}
catch{
$ErrorMessage=$_.Exception.Message
$ErrorMessage = $ErrorMessage -replace "`t|`n|`r",""
$ErrorMessage = $ErrorMessage -replace " ;|; ",";"
$lineContent = "$($Fname),$($fromfile ),$($ErrorMessage)"
Add-Content -Path $global:OutFilePathError -Value "$lineContent"
Write-Host "Skipping the file and recalling the function" -ForegroundColor Blue
}
WriteLog $title "File" $Folder.Name $fileName $FileUrl $fileSize $created $authorEmail $modified $editorEmail $MTitle $SubWeb
Write-host -f Magenta $File.Name
}
else
{
Write-Host "Skipping the matterspace :" $title " as the matterspace was not in the date range" -ForegroundColor Blue
}
}
catch{
$ErrorMessage=$_.Exception.Message
$ErrorMessage = $ErrorMessage -replace "`t|`n|`r",""
$ErrorMessage = $ErrorMessage -replace " ;|; ",";"
$lineContent = "$($Fname),$($fromfile ),$($ErrorMessage)"
Add-Content -Path $global:OutFilePathError -Value "$lineContent"
}
}
#Recursively Call the function to get files of all folders
$Ctx.load($Folder.Folders)
$Ctx.ExecuteQuery()
#Exclude "Forms" system folder and iterate through each folder
ForEach($SubFolder in $Folder.Folders | Where {$_.Name -ne "Forms"})
{
Get-FilesFromFolder -Folder $SubFolder -SubWeb $SubWeb -Mtitle $MTitle
}
}
Function Get-SPODocLibraryFiles()
{
param
(
[Parameter(Mandatory=$true)] [string] $SiteURL,
[Parameter(Mandatory=$true)] [string] $LibraryName
)
#Setup the context
$Ctx = New-Object Microsoft.SharePoint.Client.ClientContext($SiteURL)
$Ctx.Credentials = $credentials
$srcWeb = $Ctx.Web
$childWebs = $srcWeb.Webs
$Ctx.Load($childWebs)
$Ctx.ExecuteQuery()
foreach ($childweb in $childWebs)
{
try
{
#Get the Library and Its Root Folder
$Library=$childweb.Lists.GetByTitle($LibraryName)
$Ctx.Load($Library)
$Ctx.Load($Library.RootFolder)
$Ctx.ExecuteQuery()
#Call the function to get Files of the Root Folder
if($childweb.Url.ToLower() -notlike "*ehcontactus*" -and $childweb.Url.ToLower() -notlike "*ehfaqapp*" -and $childweb.Url.ToLower() -notlike "*ehquicksearch*" -and $childweb.Url.ToLower() -notlike "*ehsiteapps*" -and $childweb.Url.ToLower() -notlike "*ehsitelist*" -and $childweb.Url.ToLower() -notlike "*ehwelcomeapp*" -and $childweb.Url.ToLower() -notlike "*ehimageviewer*")
{
Get-FilesFromFolder -Folder $Library.RootFolder -SubWeb $childweb.Url -MTitle $childweb.Title
}
}
catch{
write-host "Skipping the matterpsace as the library does not exists" -ForegroundColor Blue
}
}
}
#Config Parameters
#$SiteURL= "https://impigerspuat.sharepoint.com/sites/ELeave/Eleave1/adminuat#impigerspuat.onmicrosoft.com"
$LibraryName="Documents"
#$securePassword = Read-Host -Prompt "Enter your password: " -AsSecureString
#Call the function to Get All Files from a document library
if (-not ([string]::IsNullOrEmpty($fromDate)))
{
$startDate = Get-Date (Get-Date -Date $fromDate -Format 'dd/MM/yyyy')
}
else
{
$startDate = $null;
}
if (-not ([string]::IsNullOrEmpty($toDate)))
{
$endDate = Get-Date (Get-Date -Date $toDate -Format 'dd/MM/yyyy')
}
else
{
$endDate = $null
}
Get-SPODocLibraryFiles -SiteURL $srcUrl -LibraryName $LibraryName
Have you tried running just that function and giving it the parameters it's requesting in the function?
Copy the code into a WriteLog.ps1 file and then call the script file with the parameters.
ie.
Writelog.ps1 $srcUrl $username $fromDate $toDate $folderPath $csvPath
Obviously, inputting data in place of the variables.
FWIW, pulling relevant pieces of code out of someone else's scripts is a great skill to practice. Everything you want to do has been done before, but you might have to break down someone else's work before it fits your exact enviornment.
Unfortunately it looks like you have to do this the old fashion way. The problem is the author is outputting to the log (csv) as the files are being downloaded. As opposed to downloading to a staging area first...
I suggest setting an early break-point in the code then stepping through to see exactly how it's flowing. That should give you a general idea, and enough info to start writing refactored code.
Reverse engineering is always tough, be prepared it will be methodical exercise so say the least.
Bad news: this will be an iterative process, not a single 'solve'. Nothing "wrong" with that code, but there are a few design choices that make this a challenge. It's not indented consistently and it weaves through all the variable assignments in slightly different ways. Looks better than most of my code, I'm just telling you what makes it a challenge.
Good news: At least that WriteLog function is separate. And it's really just adding content to the .csv file defined in this variable assigned here:
$global:OutFilePath = -join ($csvPath,"\Documents.csv")
(line 20 in my copy)
*
RECOMMENDATION: (this is an approach, just a guide to your final solution)
Take that existing code and drop it in an IDE to help you visually. The Windows Powershell ISE is adequate, but I would highly recommend VSCode.
Comment out that last line:
Get-SPODocLibraryFiles -SiteURL $srcUrl -LibraryName $LibraryName
So you can retain any of the other context from the script you actually want to keep.
Create a separate function named something like:
function Get-FilesFromLocalFolder ($localdir, $SubWeb, $MTitle)
to use instead of the existing function Get-FilesFromFolder. That way you can iterate through whatever directories you need, get the files, and assign variables to pass as parameters. Then when you call WriteLog, it will look very similar. Those last two parameters ($SubWeb, $MTitle) are passed just because WriteLog needs them. You could make them your own labels, or you could remove them and make them optional in WriteLog.
You could start by hard-coding values in each of required parameters for the function, and then run it to see if the output is working.
It will take you some iterations (agree with #Steven) and it is definitely a valuable exercise (agree with #TheIdesOfMark). :)
I am trying to collect user profile information for users on a machine and I was wondering if I could get it with gwmi. Here is how I get printers for the current user:Get-WmiObject win32_printer. How can I get the same info for the user "Test" on the same machine?
As it happens, I can't sleep, so I came up with these 2 functions:
function Get-UserSid {
[CmdletBinding()]
param(
[Parameter(
ParameterSetName='NTAccount',
Mandatory=$true,
ValueFromPipeline=$true,
Position=0
)]
[System.Security.Principal.NTAccount]
$Identity ,
[Parameter(
ParameterSetName='DomainAndUser',
Mandatory=$true
)]
[ValidateNotNullOrEmpty()]
[ValidatePattern('^[^\\]+$')]
[String]
$Domain ,
[Parameter(
ParameterSetName='DomainAndUser',
Mandatory=$true
)]
[ValidateNotNullOrEmpty()]
[ValidatePattern('^[^\\]+$')]
[String]
$User
)
Begin {
if ($PSCmdlet.ParameterSetName -eq 'DomainAndUser') {
$Identity = New-Object System.Security.Principal.NTAccount -ArgumentList $Domain,$User
}
}
Process {
$Identity.Translate([System.Security.Principal.SecurityIdentifier])
}
}
function Get-PrinterNameByUser {
[CmdletBinding(DefaultParameterSetName='Ambiguous')]
param(
[Parameter(
ParameterSetName='ByAccount',
Mandatory=$true
)]
[System.Security.Principal.NTAccount]
$Account ,
[Parameter(
ParameterSetName='BySID',
Mandatory=$true
)]
[System.Security.Principal.SecurityIdentifier]
$SID ,
[Parameter(
ParameterSetName='Ambiguous',
Mandatory=$true,
Position=0,
ValueFromPipeline=$true
)]
[ValidateNotNullOrEmpty()]
[String]
$Identity
)
Begin {
Write-Verbose "Parameter Set Name: $($PSCmdlet.ParameterSetName)"
if ($PSCmdlet.ParameterSetName -eq 'ByAccount') {
$SID = $Account | Get-UserSid
}
}
Process {
if ($PSCmdlet.ParameterSetName -eq 'Ambiguous') {
try {
$SID = [System.Security.Principal.SecurityIdentifier]$Identity
} catch [System.InvalidCastException] {
$Account = [System.Security.Principal.NTAccount]$Identity
$SID = $Account | Get-UserSid
}
}
Get-ChildItem -Path "Registry::\HKEY_Users\$($SID.Value)\Printers" | Select-Object -ExpandProperty Property -Unique
}
}
Usage
Get-PrinterNameByUser Test
Get-PrinterNameByUser 'domain\test'
Get-PrinterNameByUser 'S-1-S-21-65454546-516413534-4444'
All of those could be piped as well:
'Test' | Get-PrinterNameByUser
'domain\test' | Get-PrinterNameByUser
'S-1-S-21-65454546-516413534-4444' | Get-PrinterNameByUser
'S-1-S-21-65454546-516413534-4444','user1','machine\user2','domain\user3' | Get-PrinterNameByUser
Explanation
In the registry at HKU\S-ID-HERE\Printers there are some keys with properties. The property names are the printers. I wasn't able to test this on enough machines, so I wasn't certain which key(s) I should check, and whether they would be different depending on whether it was a local or network printer, etc., so I'm just getting the properties from all the keys and returning the unique ones.
The helper function Get-UserSid just provides a convenient way to get a SID from a user name.
Most of Get-PrinterNameByUser is just code to figure out what you've given it and translate it at needed. The meat of it that returns what you want is just the one line:
Get-ChildItem -Path "Registry::\HKEY_Users\$($SID.Value)\Printers" | Select-Object -ExpandProperty Property -Unique