Posts on hearn.sh https://www.hearn.us/posts/ Recent content in Posts on hearn.sh Hugo -- gohugo.io en-us Tom Hearn Tue, 17 Mar 2020 19:30:00 -0400 Coronavirus Information Security Tips https://www.hearn.us/posts/coronavirus-information-security-tips/ Tue, 17 Mar 2020 19:30:00 -0400 https://www.hearn.us/posts/coronavirus-information-security-tips/ It’s been an interesting couple of days for sure. We are all trying to figure out what this whole Coronavirus ordeal actually means to our families and our daily lives. Schools, restaurants, bars, movie theaters and basically everything else is closed and we all wish we had invested in toilet paper companies 2 weeks ago. Thankfully we have the internet to keep us all up to date (maybe too up to date! It’s been an interesting couple of days for sure. We are all trying to figure out what this whole Coronavirus ordeal actually means to our families and our daily lives. Schools, restaurants, bars, movie theaters and basically everything else is closed and we all wish we had invested in toilet paper companies 2 weeks ago.

Thankfully we have the internet to keep us all up to date (maybe too up to date!)

Unfortunately many will take advantage of the increased internet usage and overall anxious feelings that many of us have. I have received questions from family members, friends and colleagues over the past weeks about random emails, websites, programs, and weird messages.

I figured it was time to publish some of these for the world to see so I can share this link each time they come in.

Standard Best Practices Still Apply…

  • Enable Multi Factor anywhere you can. Banks, utility websites, password managers, etc. If a service you are using doesn’t offer this then you should reconsider another option.
  • Do not share your username, password, social security number, or any other personal information with anyone over the phone, email or text message
  • Keep your antivirus software up to date
  • Use a firewall on your home internet router and your local computer
  • Keep your computer, router, IoT devices, smart devices, etc updated (automatically if possible)
  • Do not use the same password for any website. Consider using something like LastPass to generate random, strong passwords for each unique website you have to use for logging in
  • Watch out for phishing attempts imitating services like Google, Microsoft and many others. This may often come from a URL that looks legitimate but is not. If you suspect it is not legitimate take a screenshot and send it to someone that is knowledgeable and can help you validate. Do not ever forward an email you suspect is a phishing attempt, virus, trojan or malware.

Be weary of Coronavirus trackers

  • There is a plethora of malware, trojans and viruses using the current epidemic as a method to create fake Coronavirus trackers to take advantage of anxious users. Only get your information and/or downloads from validated, relevant sources such as the CDC, DHHS, or your local state and county health departments.

Be weary of VPN service claims

  • There has been a surge in users purchasing VPN services over the past few years to “hide activity from ISPs” and to “block website tracking.” These promises have only increased with more users working from home. They are often misleading as you move from your ISP tracking you to the VPN service tracking you. Many of these services are operated by companies in foreign countries.
  • If you are using a paid VPN service make sure it is reputable
  • If you are using a free VPN service I would recommend you stop
  • Assume anything you are doing on a paid VPN service can be (and most likely is) tracked

Be careful when donating

  • If someone calls you asking for donations ask them for their business information and a number you can call them back. Hang up, look up their information online and if it’s legitimate give them a call back
  • Make sure you are checking the URL of any online donations and it is a validated, secure connection to provide any credit card information

A good opportunity to change your passwords

  • Consider changing some of your important passwords in case anyone has your primary account passwords. A reputable source to monitor this for free is Have I Been Pwned
  • Rotate your WiFi password in case any neighbors might have it. This is good practice every 6 months even without Coronavirus.

Don’t forget to protect your employer

  • With so many employees working from home we are at a significantly increased risk of breaches to business sytems and confidential information
  • Make sure you are keeping your work-related information on your work device
  • Shut your work laptop down, disconnect your VPN and logout when you are not working
  • Make sure you are familiar with your company’s policies around VPN, systems access and what to do if you suspect your credentials were compromised
  • Be patient with your IT staff. They are definitely stressed right now with so many users working remotely.
]]>
Cloning SVN to Git Repositories https://www.hearn.us/posts/cloning-svn-to-git/ Sun, 16 Feb 2020 19:30:00 -0400 https://www.hearn.us/posts/cloning-svn-to-git/ I’m only about 2 years late to the show, but figured I would share how easy it was to migrate from my local Subversion repository to a local Git repository. Make sure you have local filesystem access to the SVN repository and run the following commands: git svn clone --username=yourgitusername file:///mnt/nfs/fs/svn/YourRepository/ YourRepository.git cd YourRepository.git git remote add origin http://x.x.x.x:3000/yourgitusername/YourRepository.git git push -u origin master At this point you are good to start using your git repository and destroy your old SVN repository. I’m only about 2 years late to the show, but figured I would share how easy it was to migrate from my local Subversion repository to a local Git repository.

Make sure you have local filesystem access to the SVN repository and run the following commands:

git svn clone --username=yourgitusername file:///mnt/nfs/fs/svn/YourRepository/ YourRepository.git
cd YourRepository.git
git remote add origin http://x.x.x.x:3000/yourgitusername/YourRepository.git
git push -u origin master

At this point you are good to start using your git repository and destroy your old SVN repository.

]]>
Load Balancing via mod_proxy_balancer in Apache https://www.hearn.us/posts/load-balancing-via-mod-proxy/ Thu, 02 Jan 2020 19:30:00 -0400 https://www.hearn.us/posts/load-balancing-via-mod-proxy/ Configuring load balancing in Apache was, at first pass, a little cumbersome largely because I couldn’t find any relevant examples to what I was trying to do that were inclusive inside a Virtual Host. So without further ado, here is a configuraton with sticky session round robin load balancing in Apache within a Virtual Host: <VirtualHost *:80> ServerName hearn.us ServerAlias hearn.us *.hearn.us DocumentRoot /var/www/html ErrorLog logs/error_hearn.us.log CustomLog logs/access_hearn.us.log combined ProxyPreserveHost On ProxyRequests Off Header add Set-Cookie "ROUTEID=. Configuring load balancing in Apache was, at first pass, a little cumbersome largely because I couldn’t find any relevant examples to what I was trying to do that were inclusive inside a Virtual Host.

So without further ado, here is a configuraton with sticky session round robin load balancing in Apache within a Virtual Host:

<VirtualHost *:80>
    ServerName hearn.us
    ServerAlias hearn.us *.hearn.us
    DocumentRoot /var/www/html
    ErrorLog logs/error_hearn.us.log
    CustomLog logs/access_hearn.us.log combined

    ProxyPreserveHost On
    ProxyRequests Off
    Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED

    <Proxy balancer://hearn.us_cluster>
        BalancerMember http://vcosbackend1:80 route=1 # first node
        BalancerMember http://vcosbackend2:80 route=2 # second node
        Order Deny,Allow
        Allow from all
        ProxySet stickysession=ROUTEID
    </Proxy>

    ProxyPass / balancer://hearn.us_cluster/
</VirtualHost>
]]>
Apache Error: no space left on device https://www.hearn.us/posts/apache-error-no-space-left-on-device/ Mon, 18 Nov 2019 19:30:00 -0400 https://www.hearn.us/posts/apache-error-no-space-left-on-device/ I recently encountered an issue with Apache where I had plenty of disk space free, but after running sudo service httpd restart I received the error: Error: No space left on device Running ipcs -s showed that after Apache stopped I was still holding active semaphores. To Clean the semaphores I simply fixed with: for i in ``ipcs -s | awk &#39;/httpd/ {print $2}&#39;``; do (ipcrm -s $i); done Restart Apache and you should be good to go! I recently encountered an issue with Apache where I had plenty of disk space free, but after running sudo service httpd restart I received the error:

Error: No space left on device

Running ipcs -s showed that after Apache stopped I was still holding active semaphores.

To Clean the semaphores I simply fixed with:

for i in ``ipcs -s | awk '/httpd/ {print $2}'``; do (ipcrm -s $i); done

Restart Apache and you should be good to go!

]]>
Disabling ipv6 in CentOS 7 https://www.hearn.us/posts/disabling-ipv6-in-centos7/ Mon, 12 Aug 2019 19:30:00 -0400 https://www.hearn.us/posts/disabling-ipv6-in-centos7/ IPv6 can certainly cause some unexpected results in Linux land. Disabling is super easy, depending on your preference of all interfaces or a specific interface: Create a file /etc/sysctl.d/disableipv6.conf: For all interfaces: net.ipv6.conf.all.disable_ipv6 = 1 For an individual interface: net.ipv6.conf.eth0.disable_ipv6 = 1 Rename eth0 to whatever interface you would like to disable. Restart after to apply: shutdown -r now IPv6 can certainly cause some unexpected results in Linux land. Disabling is super easy, depending on your preference of all interfaces or a specific interface:

Create a file /etc/sysctl.d/disableipv6.conf:

For all interfaces:

net.ipv6.conf.all.disable_ipv6 = 1

For an individual interface:

net.ipv6.conf.eth0.disable_ipv6 = 1

Rename eth0 to whatever interface you would like to disable.

Restart after to apply:

shutdown -r now
]]>
Setup a Site to Site VPN with Ubiquiti https://www.hearn.us/posts/ubiquiti-site-to-site-vpn/ Wed, 14 Nov 2018 19:30:00 -0400 https://www.hearn.us/posts/ubiquiti-site-to-site-vpn/ I use Ubiquiti heavily in my lab environments. I have a hosted site that I keep a number of virtualization servers and storage and recently setup a formal Site to Site VPN across two ERPro-8 Ubiquiti routers. This will most likely work for anything in the Edge line. For the sake of this configuration we&rsquo;ll say this is between Router A and Router B. Router A has the external IP of X. I use Ubiquiti heavily in my lab environments. I have a hosted site that I keep a number of virtualization servers and storage and recently setup a formal Site to Site VPN across two ERPro-8 Ubiquiti routers. This will most likely work for anything in the Edge line.

For the sake of this configuration we’ll say this is between Router A and Router B.

Router A has the external IP of X.X.X.X and an internal subnet of 10.100.200.0/24

Router B has the external IP of Y.Y.Y.Y and an internal subnet of 10.200.200.0/24

Configuration is below:

Router A CLI:

configure

set vpn ipsec ike-group FOO0 lifetime 28800
set vpn ipsec ike-group FOO0 proposal 1 dh-group 14
set vpn ipsec ike-group FOO0 proposal 1 encryption aes128
set vpn ipsec ike-group FOO0 proposal 1 hash sha1

set vpn ipsec esp-group FOO0 lifetime 3600
set vpn ipsec esp-group FOO0 pfs enable
set vpn ipsec esp-group FOO0 proposal 1 encryption aes128
set vpn ipsec esp-group FOO0 proposal 1 hash sha1

set vpn ipsec site-to-site peer Y.Y.Y.Y authentication mode pre-shared-secret
set vpn ipsec site-to-site peer Y.Y.Y.Y authentication pre-shared-secret YOURSUPERSECRETANDVERYUNIQUEPSK
set vpn ipsec site-to-site peer Y.Y.Y.Y description ipsec
set vpn ipsec site-to-site peer Y.Y.Y.Y local-address X.X.X.X

set vpn ipsec site-to-site peer Y.Y.Y.Y ike-group FOO0
set vpn ipsec site-to-site peer Y.Y.Y.Y vti bind vti0
set vpn ipsec site-to-site peer Y.Y.Y.Y vti esp-group FOO0

set interfaces vti vti0 address 10.255.255.1/30
set protocols static interface-route 10.200.200.0/24 next-hop-interface vti0

commit; save

Router B CLI:

configure

set vpn ipsec ike-group FOO0 lifetime 28800
set vpn ipsec ike-group FOO0 proposal 1 dh-group 14
set vpn ipsec ike-group FOO0 proposal 1 encryption aes128
set vpn ipsec ike-group FOO0 proposal 1 hash sha1

set vpn ipsec esp-group FOO0 lifetime 3600
set vpn ipsec esp-group FOO0 pfs enable
set vpn ipsec esp-group FOO0 proposal 1 encryption aes128
set vpn ipsec esp-group FOO0 proposal 1 hash sha1

set vpn ipsec site-to-site peer X.X.X.X authentication mode pre-shared-secret
set vpn ipsec site-to-site peer X.X.X.X authentication pre-shared-secret YOURSUPERSECRETANDVERYUNIQUEPSK
set vpn ipsec site-to-site peer X.X.X.X description ipsec
set vpn ipsec site-to-site peer X.X.X.X local-address Y.Y.Y.Y

set vpn ipsec site-to-site peer X.X.X.X ike-group FOO0
set vpn ipsec site-to-site peer X.X.X.X vti bind vti0
set vpn ipsec site-to-site peer X.X.X.X vti esp-group FOO0

set interfaces vti vti0 address 10.255.255.2/30
set protocols static interface-route 10.100.200.0/24 next-hop-interface vti0

commit; save
]]>
Useful Powershell commands for Linux nerds https://www.hearn.us/posts/useful-powershell-commands-for-linux-nerds/ Tue, 23 Jun 2015 19:30:00 -0400 https://www.hearn.us/posts/useful-powershell-commands-for-linux-nerds/ I often find myself looking for a few commands I drastically miss from linux in the Windows world. Pretty basic but here they are listed by their equal parts between Linux and Windows Powershell. Note: Below I reference gc which is an alias for Get-Content. They can be interchanged. active tail tail -f file.txt gc file.txt -Wait tail last 10 tail -n10 file.txt gc file.txt | Select -last 10 head first 10 head -n10 file. I often find myself looking for a few commands I drastically miss from linux in the Windows world. Pretty basic but here they are listed by their equal parts between Linux and Windows Powershell.

Note: Below I reference gc which is an alias for Get-Content. They can be interchanged.

active tail

tail -f file.txt
gc file.txt -Wait

tail last 10

tail -n10 file.txt
gc file.txt | Select -last 10

head first 10

head -n10 file.txt
gc file.txt | select -first 10

grep

grep my.pattern
Select-String -Pattern my.pattern

word count

wc -l file.txt
gc file.txt | Measure-Object -Line

Process Listing

ps
get-process

Kill Process

kill app.exe
get-process app.exe | StopProcess
]]>
Determine Forest and Domain Functional Levels in Powershell https://www.hearn.us/posts/determine-forest-and-domain-functional-levels-in-powershell/ Thu, 06 Nov 2014 19:30:00 -0400 https://www.hearn.us/posts/determine-forest-and-domain-functional-levels-in-powershell/ A quick and easy post: Import-Module ActiveDirectory # Get the forest functional level (Get-ADForest).ForestMode # Get the domain functional level (Get-ADDomain).DomainMode A quick and easy post:

Import-Module ActiveDirectory

# Get the forest functional level
(Get-ADForest).ForestMode

# Get the domain functional level
(Get-ADDomain).DomainMode
]]>
Permission Denied Because Search Permissions Are Missing https://www.hearn.us/posts/permission-denied-search-permissions/ Mon, 12 May 2014 19:30:00 -0400 https://www.hearn.us/posts/permission-denied-search-permissions/ I have been battling with an issue on Fedora Core 20 and Apache recently with an issue around receiving this error when trying to mount a virtual host in Apache and setting the DocumentRoot to a share under /mnt. The error is: [Mon May 12 21:42:02.632785 2014] [core:error] [pid 3974] (13)Permission denied: [client 192.168.1.200:50447] AH00035: access to / denied (filesystem path &#39;/mnt/html&#39;) because search permissions are missing on a component of the path I used chgrp to set the permissions on /mnt and /mnt/html and that didn&rsquo;t help me at all, I was still receiving the same message. I have been battling with an issue on Fedora Core 20 and Apache recently with an issue around receiving this error when trying to mount a virtual host in Apache and setting the DocumentRoot to a share under /mnt.

The error is:

[Mon May 12 21:42:02.632785 2014] [core:error] [pid 3974] (13)Permission denied: [client 192.168.1.200:50447] AH00035: access to / denied (filesystem path '/mnt/html') because search permissions are missing on a component of the path

I used chgrp to set the permissions on /mnt and /mnt/html and that didn’t help me at all, I was still receiving the same message.

I even tried setting to a global 777 across the board for apache (this is a non-production server) and still no positive result.

The issue in the end was SELinux preventing access outside of it’s defined default scope for Apache.

Running this fixed my issue:

chcon -R -h -t httpd_sys_content_t /mnt

This could be applied to other directory roots outside of the default /var/www as well.

]]>
Google Apps to Office 365: Duplicating the Google Apps Send Mail As Functionality https://www.hearn.us/posts/google-apps-to-0365-duplicating-sendas/ Fri, 01 Nov 2013 19:30:00 -0400 https://www.hearn.us/posts/google-apps-to-0365-duplicating-sendas/ During our recent Google Apps to Office 365 migration we realized after the migration that there was a key feature that was missing in Office 365: the ability to send from email aliases from a single account (without having a shared mailbox) In Google Apps this is a very simple feature and easily configured in the settings page. In Office 365 that is not the case. Receiving from multiple aliases is easy by just added that email to the SMTP aliases for a user account. During our recent Google Apps to Office 365 migration we realized after the migration that there was a key feature that was missing in Office 365: the ability to send from email aliases from a single account (without having a shared mailbox)

In Google Apps this is a very simple feature and easily configured in the settings page.

In Office 365 that is not the case. Receiving from multiple aliases is easy by just added that email to the SMTP aliases for a user account.

The problem occurs when you want to send from a single account, but as another user. We use aliases heavily, so we don’t want to pay for the roughly 15 accounts that we send from (but distribute to multiple different users).

Googling (or Binging, ahem) led to have a ton of responses, but none that wouldn’t yield the recipient seeing alias@domain.com on behalf of realaccount@otherdomain.com (common behavior for those Exchange admins out there).

The other disclaimer here is that this is not an officially supported method by any Microsoft documentation that I found, rather some applied Exchange on-premise knowledge that came in handy.

Unfortunately, this also only works in the actual Outlook client, not in the Office 365 OWA mail client.

This solution requires you to fire up remote Powershell to connect to Exchange Online (by this point your execution policy should already be allowing connectivity remotely)

To create your Exchange Online Remote Powershell session do the following:

$LiveCred = Get-Credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell/ -Credential $LiveCred -Authentication Basic -AllowRedirection
Import-PSSession $Session

Give it a minute to parse and load all required commandlets. while you are waiting you can complete the steps below

Now fire up the Exchange Online web page.

For each email address you want to send as:

  1. Ensure that your receiving account does not have an SMTP alias in the email addresses defined that you would like to send as
  2. Go to the recipients tab
  3. To to the contact sub-tab
  4. Create an external contact with the email address you would like to send as. Please note that the Display Name cannot be the same as any other account (it will let you add it, but in the rule creation for routing it won’t let you proceed)
  5. Go to the mail flow tab
  6. Create a new blank rule
  7. Name the rule as you would like
  8. Select: Apply this rule if the recipient is… and select the external contact you created
  9. Select: Redirect the message to… and select your recipient account (what you login as)
  10. Leave all other options as default and select save
  11. Wait around 10 minutes to ensure the rule has propagated across all nodes
  12. Now open your loaded powershell prompt back up
  13. Run the following command: Add-RecipientPermission alias@anotherdomain.com -AccessRights SendAs -Trustee loginaccount@domain.com
  14. Wait around 10 minutes to ensure the rule has propagated across all nodes
  15. Open up Microsoft Outlook
  16. Create a new message
  17. Click the from button, and select Other E-mail addresses…
  18. Click the from button in the dialog that opens and select the external contact you created in step 4

Rinse and repeat this process for each alias you would like to send as.

It isn’t pretty, but it gets the job done!

]]>
Resolving CURLOPT_FOLLOWLOCATION cannot be activated with PHP and CURL https://www.hearn.us/posts/resolving-curlopt-followlocation-cannot-be-activated/ Thu, 06 Jun 2013 19:30:00 -0400 https://www.hearn.us/posts/resolving-curlopt-followlocation-cannot-be-activated/ We were recently using a class we had created to access RETS/MLS in PHP which required that both cookies could be written locally and CURLOPT_FOLLOWLOCATION was enabled. The actual error was: CURLOPT_FOLLOWLOCATION cannot be activated when safe_mode is enabled or an open_basedir is set in /var/file/location We are running this particular site as a virtual host on Apache. To both disable safe mode as well as remove the open_basedir issue follow these instructions. We were recently using a class we had created to access RETS/MLS in PHP which required that both cookies could be written locally and CURLOPT_FOLLOWLOCATION was enabled.

The actual error was:

 CURLOPT_FOLLOWLOCATION cannot be activated when safe_mode is enabled or an open_basedir is set in /var/file/location

We are running this particular site as a virtual host on Apache.

To both disable safe mode as well as remove the open_basedir issue follow these instructions. Please note that safe mode is going away in future versions of PHP.

  1. Fire up your UNIX shell of choice, navigate to the location of your httpd.conf file for the virtual host (or your single httpd.conf file with multiple virtual hosts enabled):
cd /var/www/virtualhosts/domain.com/conf
  1. Edit the file
vi httpd.conf
  1. Add the following 2 lines to the required location in your httpd.conf file and save the edits:
php_admin_flag safe_mode off
php_admin_flag open_basedir none
  1. Reload the apache service
service httpd reload

You’re all set!

Optionally you could also change this configuration in the default php.ini file, but this is a much safer and restricted approach.

]]>
Retrieving User Name and MetaData through WCF Web Services for SharePoint 2010 https://www.hearn.us/posts/retrieving-username-and-metadata-sharepoint-2010/ Mon, 04 Mar 2013 19:30:00 -0400 https://www.hearn.us/posts/retrieving-username-and-metadata-sharepoint-2010/ We have been writing some automated jobs at work to pull user data and compare against Active Directory using WCF services for SharePoint. The important part of working with Active Directory, is of course, the dreaded username (aka sAMAccountName). We noticed that when we pulled user information from a list the ModifiedBy, CreatedBy, etc were all blank. They did, however, have a ModifiedById and CreatedById which was a single integer. We have been writing some automated jobs at work to pull user data and compare against Active Directory using WCF services for SharePoint.

The important part of working with Active Directory, is of course, the dreaded username (aka sAMAccountName).

We noticed that when we pulled user information from a list the ModifiedBy, CreatedBy, etc were all blank. They did, however, have a ModifiedById and CreatedById which was a single integer.

As it turns out, there is a list in the DataContext called UserInformationList which can be used to pull users based on their ID.

See the example below:

MyNamespace.MyDataContext dc = new MyNamespace.SiteDataContext(new Uri("http://sharepoint.nerdyhearn.com/sites/departments/mydepartment/_vti_bin/ListData.svc/"));
dc.Credentials = System.Net.CredentialCache.DefaultCredentials;

var mylist = dc.MyList;

// I like hard-typed lists for user information instead of var-based data
List<MyNamespace.UserInformationListItem> allSPUsers = dc.UserInformationList.ToList<MyNamespace.UserInformationListItem>();

foreach (var listItem in mylist) {
    int modifiedById = listItem.modifiedById;

    UserInformationListItem allUserData = (from usercheck in allSPUsers
        where usercheck.Id.Equals(modifiedById)
        select usercheck).FirstOrDefault<UserInformationListItem>();
        
    string username = allUserData.UserName;
    string name = allUserData.Name;
    // more properties to be read here
}

Simple enough, but Googling didn’t help me much on this, so figured I’d share

]]>
Up the creek without a paddle: Taking over a deserted SharePoint 2007 installation https://www.hearn.us/posts/taking-over-a-deserted-sharepoint-installation/ Thu, 27 Dec 2012 19:30:00 -0400 https://www.hearn.us/posts/taking-over-a-deserted-sharepoint-installation/ I&rsquo;ve been working on a client site for a few weeks and coming into the project they knew absolutely nothing about their current environment asides from the server name. This meant we didn&rsquo;t have the Farm admin password, and didn&rsquo;t have any current employees as farm or collection admins. To regain control of the environment I went through the following steps: Reset the service account passwords by following the Microsoft KB article here Once we re-gained farm login after resetting credentials, we had another interesting caveat. I’ve been working on a client site for a few weeks and coming into the project they knew absolutely nothing about their current environment asides from the server name. This meant we didn’t have the Farm admin password, and didn’t have any current employees as farm or collection admins. To regain control of the environment I went through the following steps:

  1. Reset the service account passwords by following the Microsoft KB article here

Once we re-gained farm login after resetting credentials, we had another interesting caveat. BUILTIN\Administrators and the farm admin account had been removed from the Farm Admins group so we still couldn’t access content on any site collections.

They had also changed their theme, so in central admin the menus to add users were not working. Our only option from this point was to add our accounts using stsadm.

For both steps below also pay attention to if the site is using SSL or not (you’ll need to change http:// or https:// depending on the site collection)

  1. Add users to the Farm Administrators group using STSADM:
stsadm -o adduser -url http://centraladminservername:port -userlogin DOMAIN\user -useremail user@email.com -group "Farm Administrators" -username "DOMAIN\user"
  1. Set site collection ownership using STSADM: You’ll need to do this for each site collection you would like access to (My sites, Web Applications, etc)
stsadm -o siteowner -url http://site:port -ownerlogin DOMAIN\user -secondarylogin DOMAIN\user

Keep in mind that site collections can only have 2 owners, and resetting ownership may have adverse affects if the account you remove was being used as something other than an individual user.

]]>
Search and Replace in MySQL https://www.hearn.us/posts/search-and-replace-in-mysql/ Sun, 09 Oct 2011 19:30:00 -0400 https://www.hearn.us/posts/search-and-replace-in-mysql/ I often find myself looking for the proper syntax while attempting to do a search and replace in MySQL, and figured that I would help out by posting the syntax for someone else in the same situation: UPDATE `schema`.`table` set column = replace(column, &#34;find&#34;, &#34;replace&#34;); That syntax will find any values &ldquo;find&rdquo; within the schema and table, and replace them with &ldquo;replace&rdquo; A pretty short post but hopefully helpful to some of you out there. I often find myself looking for the proper syntax while attempting to do a search and replace in MySQL, and figured that I would help out by posting the syntax for someone else in the same situation:

UPDATE `schema`.`table` set column = replace(column, "find", "replace");

That syntax will find any values “find” within the schema and table, and replace them with “replace”

A pretty short post but hopefully helpful to some of you out there.

]]>
Web Form To Help Audit Permissions In SharePoint 2007/MOSS https://www.hearn.us/posts/web-form-to-audit-permissions-in-sharepoint/ Wed, 06 Oct 2010 19:30:00 -0400 https://www.hearn.us/posts/web-form-to-audit-permissions-in-sharepoint/ Determining an overall view of SharePoint&rsquo;s permissions can be quite a nasty task. Without the use of paid tools it becomes almost impossible to view the breakdown of an entire Site Collection&rsquo;s permission structure. We recently came across the need to determine permissions of a number of sites that are customer-facing as we are going to re-organize the site and I decided it was time to build a tool to help us in this process once and for all that we could use in the future (as this is not an un-common request). Determining an overall view of SharePoint’s permissions can be quite a nasty task. Without the use of paid tools it becomes almost impossible to view the breakdown of an entire Site Collection’s permission structure.

We recently came across the need to determine permissions of a number of sites that are customer-facing as we are going to re-organize the site and I decided it was time to build a tool to help us in this process once and for all that we could use in the future (as this is not an un-common request).

I determined the easiest way would be to create an aspx page that could be loaded in the central admin realm.

This page is .NET 3.5-based and will iterate the entire farm, all site collections, and all sites, breaking down the permissions and roles for each site, where inheritance is broken, and when it is broken, what the updated permissions are. It even goes into list and list items permissions and inheritance. The amount of information it generates is quite overwhelming, so I ended up using this file to output directly to XML and then transformed the results as I needed via XSLT.

Without further ado, here is the code. You can simply place this entire code-integrated file in C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\ADMIN. In my case I named the file auditor.aspx so it can be accessed directly from the farm’s central admin site.


<%@ Page Language="C#" ContentType="application/xml"%> 
<%@ Import Namespace="Microsoft.SharePoint.Administration" %> 
<%@ Import Namespace="Microsoft.SharePoint" %> 
<%@ Import Namespace="System.Linq" %>
<%@ Import Namespace="System.Xml.Linq" %>
<%@ Import Namespace="System.Xml" %> 
<%@ Import Namespace="System.IO" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> 

<script runat="server"> 
    protected override void OnLoad(EventArgs e){ 
        try
        {
            SPFarm thisFarm = SPFarm.Local;
            SPWebService service = thisFarm.Services.GetValue<SPWebService>("");
            XDocument doc = new XDocument();
            XElement root = new XElement("webapplications");

            try
            {                
                foreach (SPWebApplication webApp in service.WebApplications)
                {
                    XElement webappelem = new XElement("webapplication");
                    XAttribute attrname = new XAttribute("name", webApp.DisplayName);
                    webappelem.Add(attrname);

                    try
                    {
                        XElement sites = new XElement("sites");

                        foreach (SPSite siteCollection in webApp.Sites)
                        {
                            XElement site = new XElement("site");
                            site.Add(new XAttribute("url", siteCollection.Url));
                            GetWebs(siteCollection.AllWebs, site);
                            sites.Add(site);
                        }

                        webappelem.Add(sites);
                    }
                    catch (Exception siteError)
                    {
                        webappelem.Add(new XElement("error", siteError.Message));
                    }
                    root.Add(webappelem);
                }
            }
            catch (Exception webAppError)
            {
                root.Add(new XElement("error", webAppError.Message));
            }

            doc.Add(root);
            XmlWriter writer = XmlWriter.Create(Response.OutputStream);
            doc.WriteTo(writer);
            writer.Close();
        }
        catch (Exception ex)
        {
            System.Web.HttpContext.Current.Response.Write(ex.Message);
        }
    }

    public XElement GetRoleAssignments(SPRoleAssignmentCollection racollection, SPWeb web)
    {
        XElement raelem = new XElement("roleassignments");

        foreach (SPRoleAssignment ra in racollection)
        {
            XElement tempra = new XElement("roleassignment");

            tempra.Add(new XAttribute("name", ra.Member.Name));
            tempra.Add(new XAttribute("id", ra.Member.ID));

            try
            {
                SPGroup group = web.Groups.GetByID(ra.Member.ID);
                tempra.Add(new XAttribute("isgroup", "True"));
            }
            catch
            {
                tempra.Add(new XAttribute("isgroup", "False"));
            }

            XElement rdtemp = new XElement("roledefinitions");

            foreach (SPRoleDefinition rd in ra.RoleDefinitionBindings)
            {
                XElement role = new XElement("role");
                role.Add(new XAttribute("name", rd.Name));
                role.Add(new XAttribute("permissions", rd.BasePermissions.ToString()));
                rdtemp.Add(role);
            }
            tempra.Add(rdtemp);
            raelem.Add(tempra);
        }
        return raelem;
    }

    public void GetWebs(SPWebCollection allWebs, XElement site)
    {
        XElement webs = new XElement("webs");

        try
        {
            foreach (SPWeb web in allWebs)
            {
                XElement webelem = new XElement("web");
                webelem.Add(new XAttribute("url", web.Url));
                webelem.Add(new XAttribute("title", web.Title));
                webelem.Add(new XAttribute("WebTemplateID", web.WebTemplateId.ToString()));
                webelem.Add(new XAttribute("WebTemplateName", web.WebTemplate));

                XElement grpelem = new XElement("groups");

                foreach (SPGroup group in web.Groups)
                {
                    XElement tempgrp = new XElement("group");

                    tempgrp.Add(new XAttribute("name", group.Name));
                    tempgrp.Add(new XAttribute("id", group.ID));

                    string owner = group.Owner.ID.ToString();

                    try
                    {
                        SPUser user = web.Users.GetByID(group.Owner.ID);
                        owner = user.Name;
                    }
                    catch
                    {
                        try
                        {

                            SPGroup groupcheck = web.Groups.GetByID(group.Owner.ID);
                            owner = groupcheck.Name;
                        }
                        catch { }
                    }

                    tempgrp.Add(new XAttribute("owner", owner));

                    XElement members = new XElement("members");

                    foreach (SPUser user in group.Users)
                    {
                        XElement tempmember = new XElement("member");
                        tempmember.Add(new XAttribute("name", user.Name));
                        tempmember.Add(new XAttribute("id", user.ID));
                        members.Add(tempmember);
                    }

                    tempgrp.Add(members);
                    grpelem.Add(tempgrp);
                }
                webelem.Add(grpelem);
                webelem.Add(GetRoleAssignments(web.RoleAssignments, web));

                XElement lists = new XElement("lists");

                foreach (SPList list in web.Lists)
                {
                    XElement listelem = new XElement("list");
                    listelem.Add(new XAttribute("title", list.Title));
                    listelem.Add(new XAttribute("uniqueroleassignments", list.HasUniqueRoleAssignments));

                    if (list.HasUniqueRoleAssignments)
                    {
                        listelem.Add(GetRoleAssignments(list.RoleAssignments, web));
                    }

                    XElement itemselem = new XElement("items");

                    foreach (SPListItem item in list.Items)

                    {
                        XElement itemelem = new XElement("item");

                        try
                        {
                            itemelem.Add(new XAttribute("title", item.Title));
                        }
                        catch {
                            try
                            {
                                itemelem.Add(new XAttribute("title", item.DisplayName));
                            }
                            catch {
                                itemelem.Add(new XAttribute("title", "Not Available"));
                            }
                        }

                        if (item.HasUniqueRoleAssignments)
                        {
                            itemelem.Add(GetRoleAssignments(item.RoleAssignments, web));
                        }
                        itemselem.Add(itemelem);
                    }
                    listelem.Add(itemselem);
                    lists.Add(listelem);
                }
                webelem.Add(lists);
                webs.Add(webelem);
                web.Dispose();
            }
        }
        catch (Exception webError)
        {

            webs.Add(new XElement("error", webError.Message));

        }

        site.Add(webs);

    }
</script>
]]>
Creating an Organizational Chart Using C#: Part 2 https://www.hearn.us/posts/creating-an-org-chart-using-csharp-part2/ Thu, 18 Jun 2009 19:30:00 -0400 https://www.hearn.us/posts/creating-an-org-chart-using-csharp-part2/ In part 1 of this series, I went over how to actually establish the tree layout that would be used to create an organizational chart using C#. Now that we have established we have a loaded tree structure, we simply need to build the chart itself. To simplify the explanation of this process, I have broken the drawing out into two sections. The first section, BuildBlock, simply builds the actual block which will have each Employee&rsquo;s name and title information, with a background color and a small black border around the outside. In part 1 of this series, I went over how to actually establish the tree layout that would be used to create an organizational chart using C#.

Now that we have established we have a loaded tree structure, we simply need to build the chart itself.

To simplify the explanation of this process, I have broken the drawing out into two sections.

The first section, BuildBlock, simply builds the actual block which will have each Employee’s name and title information, with a background color and a small black border around the outside. By breaking this out of the process, it makes the code much easier to follow.

I will comment inline in the method, so here is the code for BuildBlock:

// We want to return a bitmap of each Employee class
private Bitmap BuildBlock(Employee employee)
{
  int width = 250;
  int height = 80;

  // Create a new Bitmap which is is specified by the height and width above
  Bitmap canvas = new Bitmap(width, height);

  // Create a drawable Graphics object from the bitmap
  Graphics gfx = Graphics.FromImage(canvas);

  // Fill the rectangle with a background of gray
  gfx.FillRegion(Brushes.Gray, new Region(new Rectangle(0, 0, width, height)));

  // Draw the border around the outside
  gfx.DrawRectangle(new Pen(Brushes.Black, 1), new Rectangle(0, 0, width - 1, height - 1));

  // Establish our two font objects, for the name and title respectively
  Font fontname = new Font("Helvetica", 14, FontStyle.Bold, GraphicsUnit.Pixel);
  Font fonttitle = new Font("Helvetica", 10, FontStyle.Italic, GraphicsUnit.Pixel);

  int namewidth = 0;
  int nameheight = 0;
  int titlewidth = 0;
  int titleheight = 0;

  // Get the height and widths of each text blob, with their fonts, so we can center them in the block
  namewidth = (int)gfx.MeasureString(employee.Name, fontname).Width;
  nameheight = (int)gfx.MeasureString(employee.Name, fontname).Height;
  titlewidth = (int)gfx.MeasureString(employee.Title, fonttitle).Width;
  titleheight = (int)gfx.MeasureString(employee.Title, fonttitle).Height;

  int namex = 0;
  int namey = 0;
  int titlex = 0;
  int titley = 0;

  // Now determine the center for names and titles with the widths and heights of the title and name font sections
  namex = (width / 2) - (namewidth / 2);
  namey = (height / 2) - ((nameheight + titleheight) / 2);
  titlex = (width / 2) - (titlewidth / 2);
  titley = (height / 2) - ((nameheight + titleheight) / 2) + nameheight;

  // Draw the actual text
  gfx.DrawString(Name, fontname, Brushes.DarkBlue, namex, namey);
  gfx.DrawString(Title, fonttitle, Brushes.Black, titlex, titley);

  // Flush the output to the bitmap
  gfx.Flush();

  // Establish a padding bitmap, which will have 10 pixels around the entire image
  // Padding makes it look much better so they are not cramping and allows for easier drawing of lines
  Bitmap padding = new Bitmap(width + 20, height + 20);

  // Make a graphics object from the padding bitmap
  Graphics padGfx = Graphics.FromImage(padding);

  // Center the image in the padding canvas
  padGfx.DrawImage(canvas, new Point(10, 10));

  // Flush the output to the bitmap
  padGfx.Flush();
  return padding;
}

The second aspect of drawing the tree, is drawing the entire tree itself. If you look at the structure of a tree, each tree is really a branch, even the tree itself, from the larger aspect.

To make the drawing logical and much easier to understand, if you break each grouping into a branch, you can then apply logic to each branch and traverse upwards until your tree is complete.

The aptly named BuildBranch is the real “meat” of our organizationl chart builder, so without further ado, here is the recursive code:

private Bitmap BuildBranch(Employee Manager)
{
  // In each case of BuildBranch, Manager is really just the parent of all the children

  // Get the block of the manager
  Bitmap manager = BuildBlock(Manager);

  // Now do the same for each child, obtaining a list of all Bitmap children
  List<Bitmap> children = new List<Bitmap>();
  foreach (Employee reportee in Manager.Children)
  {
  children.Add(BuildBlock(reportee));
  }

  int canvasWidth = 0;
  int canvasHeight = 0;

  // Figure out your final heights and widths
  // Do not forget that a branch could also just be a single employee without any children,
  // essentially rendering a branch the same as a block
  if (children.Count > 0)
  {
    foreach (Bitmap child in children)
    {
      canvasHeight += child.Height;
      canvasWidth = manager.Width;
    }

    // Always increase the height by the manager
    canvasHeight += manager.Height;
  }
  else
  {
    canvasWidth = manager.Width;
    canvasHeight = manager.Height;
  }

  // Create the bitmap to the correct size
  Bitmap result = new Bitmap(canvasWidth, canvasHeight);

  // Now create a Graphics object from that bitmap
  Graphics gfx = Graphics.FromImage(result);

  // Center the manager in the entire branch image
  int managerx = (canvasWidth / 2) - (manager.Width / 2);

  // If the manager has children, then you want to draw a line down to connect the branches below
  if (children.Count > 0)
  {
    Graphics liner = Graphics.FromImage(manager);
    liner.DrawLine(new Pen(Brushes.Black, 2), new Point(manager.Width / 2, 90), new Point(manager.Width / 2, 100));
    liner.Flush();
  }

  // Draw a line from the center of the manager up (to connect to the next block)
  Graphics liner = Graphics.FromImage(manager);
  liner.DrawLine(new Pen(Brushes.Black, 2), new Point(manager.Width / 2, 0), new Point(manager.Width / 2, 10));
  liner.Flush();

  gfx.DrawImage(manager, new Point(managerx, 0));

  // Draw lines in between org levels to connect each child to the manager
  int childx = 0;
  foreach (Bitmap child in children)
  {
    gfx.DrawImage(child, new Point(childx, manager.Height));
    gfx.DrawLine(new Pen(Brushes.Black, 2), new Point(childx + (child.Width / 2), manager.Height), new Point(managerx + (manager.Width / 2), manager.Height));
    childx += child.Width;
  }

  // Dump the graphics object to the bitmap
  gfx.Flush();

  // Give us the full Org Chart!
  return result;
}

That’s it, now you have your organizational chart!

]]>
Creating an Organizational Chart Using C#: Part 1 https://www.hearn.us/posts/creating-an-org-chart-using-csharp-part1/ Wed, 17 Jun 2009 19:30:00 -0400 https://www.hearn.us/posts/creating-an-org-chart-using-csharp-part1/ An organizational chart is something that many, well, organizations use. It is a very common request and often something that takes a long time for someone to create in something like Visio. Obviously the larger your company the more time-saving that is involved when these can be generated automatically. This is the first part of a two-part series in which I would like to simply establish the basis, the technical layout, and the technologies that will be used to create the chart. An organizational chart is something that many, well, organizations use. It is a very common request and often something that takes a long time for someone to create in something like Visio. Obviously the larger your company the more time-saving that is involved when these can be generated automatically.

This is the first part of a two-part series in which I would like to simply establish the basis, the technical layout, and the technologies that will be used to create the chart.

For the sake of this example, I will simply use a very simple Employee class, which will have their full name and their title. Each Employee instance can also hold any number of children Employees of the same type.

This class layout allows us to create the tree in concept, populate a tree-based Employee structure, and then populate the chart based on this layout.

The very simple code for the Employee class is as follows:

public class Employee
{
  private string m_Name = string.Empty;
  private string m_Title = string.Empty;
  private List m_Children = new List();

  public string Name
  {
    get
    {
      return m_Name;
    }
    set
    {
      m_Name = value;
    }
  }

  public string Title
  {
    get
    {
      return m_Title;
    }
    set
    {
      m_Title = value;
    }
  }

  public List Children
  {
    get
    {
      return m_Children;
    }
    set
    {
      m_Children = value;
    }
  }
}

This class can then be populated through a recursive call by whatever means you find necessary. The data populating this tree is most-often Active Directory or LDAP, but could also be SQL, XML, a Web Service, or anything else that is data-accessible.

Let’s use LDAP for our example, and the recursive call to populate our Employee tree would be something similiar to the following:

public Employee BuildTree(Employee Manager)
{
  // LDAP is out of the scope of this article, so let's assume this works properly
  List reports = LdapSearcherClass.FindDirectReports(Manager);
  foreach (Employee child in reports)
  {
    Manager.Children.Add(BuildTree(child));
  }
  return Manager;
}

To get the entire tree, we would simply call this as follows: Employee TopDog = BuildTree(new Employee(“TopDog Name”));

We now have the structure we need for the tree, and the method of populating this structure, which leaves us with an entirely populated tree of type Employee that we can use in Part 2 to actually draw the chart.

]]>