[{"content":"I have a lot going on. There\u0026rsquo;s Microsoft MVP stuff, Community Days, and organizing events like M365 NYC, plus personal projects, the house and the kids. That\u0026rsquo;s a lot of activity to track, and most of it lives in email.\nSo I wanted a personal assistant. Something that could consolidate my emails, give me action items, and whittle down my calendar. If I needed a meeting with someone, I wanted to tell it to send them my Calendly link so they could pick a time. And I wanted to do all of that by voice, from my phone.\nIt took three tries to get there.\nOpenClaw: Amazing Until the Subscription Went Away # I started with OpenClaw when people started talking about it. I set it up on a Linux machine, hooked it up to WhatsApp, and started talking to it and training it.\nIt was amazing. It was also clunky to get started with, and I built a lot of my own tools and connectors to make it work. But it worked for a really long time.\nOpenClaw gave me that first taste of a major productivity boost in areas of my life other than coding. I saw the power of an agentic OS and all that nonsense people were talking about.\nThen Claude stopped letting you use a subscription with OpenClaw. You had to pay separately, using API tokens, and that was a deal breaker. I was already committed to $100 a month for coding, and I wasn\u0026rsquo;t going to pay more on top of that. That was the end of OpenClaw.\nGo check it out here: OpenClaw\nHermes Agent: A Lot of Work to Keep Running # Next I moved to Hermes Agent, where I could keep using my Claude license for a while. I also tried free models and OpenAI, but they just weren\u0026rsquo;t as good. Claude was so far ahead with Opus at the time that it was hard to use anything else. I also don\u0026rsquo;t have strong enough hardware to run local models with OpenClaw or Hermes, and I really just wanted the most intelligent model at my disposal.\nI didn\u0026rsquo;t want to play games messing around with less capable models. I wanted something intelligent enough that I could give it a directive, without much description, and have it do the whole thing on its own. That\u0026rsquo;s the power.\nI spent a long time beefing it up, building my own connectors and plugins. I gave it access to my emails and hooked it up to Pushbullet so it could send and read messages on my phone. I could use it by voice through WhatsApp and Telegram, and it really worked as a communications assistant and a task tracker for my personal projects.\nThat was convenient, and it worked well for a while. Then it started giving me trouble here and there. I\u0026rsquo;d have to fix it, update it, or reboot the Linux box. I had an extra machine in the room across the hall and another server in the back room, so that\u0026rsquo;s a lot of extra equipment. Since I couldn\u0026rsquo;t use Claude with it, it ran on a low subscription and would run out of credits, and I didn\u0026rsquo;t want to pay for another one.\nEventually it just fizzled out.\nGo check it out here: Hermes Agent\nGrok Bot: It Has Its Own Computer # Recently, OpenAI came out with dots, its version of this, and around the middle of October, I saw a video about Grok Bot. I really just needed something semi-autonomous, with connectors, where I didn\u0026rsquo;t have to mess with too many things. It needed its own computer, too. Grok Bot runs on its own computer in the cloud, so there\u0026rsquo;s no box for me to reboot, and it hit all the buttons.\nIt has connectors for my email accounts: Outlook, Hotmail, Gmail, an IMAP server, and another Microsoft 365 account, plus my Community Days account. It\u0026rsquo;s connected to my time tracker so I can log time for personal projects, and to Pushbullet so it can send a text in an emergency. It also has an app, with a fully voice-activated mode, so I can just talk to it back and forth. Most of what I do is by voice now anyway, including writing these articles.\nHere\u0026rsquo;s what I\u0026rsquo;ve had it do so far, all for personal projects:\nEmail consolidation. All those inboxes get boiled down into one place. Task tracking. It keeps track of what I need to do. Coordination. It sends out meetings and replies to them. A Craigslist post. It logged in, posted for me, and let me know when someone got back to me. Finding a tree company. It found three, then emailed them directly for a quote. Site logins. It registered me for a couple of things and switched some DNS settings on my personal blog. I\u0026rsquo;m on the entry plan, Premium, which is $30 a month. With general emailing and coordination, I\u0026rsquo;m usually at about halfway through my limit. I don\u0026rsquo;t have it do anything hardcore, and I don\u0026rsquo;t have it code for me.\nGo check it out here: Grok Bot\nWhere Claude Still Wins # Grok Bot is very capable, but when I\u0026rsquo;m sitting at the computer, Claude is still better.\nI pop open Claude, dictate what I want, and it opens the browser and does the same thing. I can monitor it a lot closer. I can also log in myself and let it pick up from there, so I\u0026rsquo;m not sharing credentials with it. That makes me more comfortable, and I use Claude for anything where I really want control and don\u0026rsquo;t want an agent to have access.\nGrok Bot stores your credentials, encrypted, so it can log you into sites over and over. That\u0026rsquo;s convenient, but it\u0026rsquo;s worth thinking about before you hand over the keys.\nWhere I Landed # Agent What I liked Why I moved on OpenClaw Amazing once it was trained Subscriptions stopped working, and API tokens cost too much Hermes Agent Very capable once I built it up Constant fixing, extra hardware, and weaker models on a low plan Grok Bot Connectors, its own computer, voice I\u0026rsquo;m still using it I still use Claude Code for most of my tasks while I\u0026rsquo;m sitting at the desk. Grok Bot is great on the road, and for lower-level tasks like communication. When I need to do the heavier lifting, like configuring things, I use Claude.\n","date":"Nov 6, 2026","image":"/images/agents-i-experimented-with/agents-i-experimented-with-hero_hu_be287dc303f25fbf.webp","permalink":"/2026/11/06/agents-i-experimented-with/","section":"Post","summary":"I wanted a personal assistant I could talk to, to keep up with my email, calendar and projects. It took three AI agents, two extra computers, and a lot of my …","title":"OpenClaw, Hermes, Grok Bot: My Search for a Personal AI Assistant","type":"posts"},{"content":"In my lease routing post, single-purpose documents worked great, and I mentioned that files with a bunch of things jammed into them don\u0026rsquo;t. This post is about those files.\nThese were giant archive packets: leases, rental agreements, cancellations, letters, all scanned into one PDF. We had to find the lease data across about 25,000 files, without paying someone to read them all or paying a top-tier AI model to do it.\nThe short version: a cheap AI model plus simple checks worked about as well as a far more expensive model.\nThe Architecture # The work happens in three places: SharePoint, a local machine, and Microsoft Foundry (the Azure service that used to be called Azure AI Foundry).\nWhere each part runs SharePoint holds the files. The packets sit in archive folders across the property sites. PnP PowerShell signs in, crawls those folders, and downloads a few files at a time, as the local queue has room. A local machine does the heavy lifting. Tesseract, page picking, and the checks all run here. Up to three PowerShell windows run at once, each working on a different property. Microsoft Foundry does the reading. gpt-5-mini and gpt-5.5 are both deployed here. The tool sends only the selected pages and gets back the data, the page it\u0026rsquo;s on, and a quote. When a file matches a tenant, PnP PowerShell copies it to that tenant\u0026rsquo;s Lease folder back in SharePoint.\nWhy We Ran It Locally # This first bulk pass is a one-time job, so it runs on a local machine as a standalone tool. There are no new queues or Azure Functions, and it can\u0026rsquo;t touch the live document router. We may move it to the cloud later.\nLocal doesn\u0026rsquo;t mean offline. Tesseract and the checks run on the machine, and the selected pages go to Microsoft Foundry.\nHow It Works # A simplified view of the process 1. Queue the files. A crawl of SharePoint builds a list of archive files. Each one downloads and moves through local folders: inbox, OCR, AI, then filed, review, or errors. A stopped run can pick up where it left off.\n2. Get text from every page. If a page has under 20 characters of text, Tesseract reads it at 200 DPI. Otherwise we keep the text already in the PDF. A page counts as scanned if a single image covers at least half of it, whatever text it claims to have.\n3. Pick the pages worth sending. The first two pages, pages that identify a lease, pages with the wording we\u0026rsquo;re looking for, and each lease cover plus the next four pages. There\u0026rsquo;s no fixed page cap.\n4. First read with a cheap model. Every file goes to gpt-5-mini in Microsoft Foundry at low reasoning effort. Digital pages go as text. Scanned pages go as 150 DPI images with the page number stamped on them. It returns the document type, tenant, the data we want, the page it\u0026rsquo;s on, and a word-for-word quote.\n5. Check the answer. This is where most of the value is:\nOCR vote on numbers. The scanner\u0026rsquo;s text, Tesseract at 200 DPI, and a 400 DPI crop around the number all weigh in. If they point to an obvious misread, the number gets corrected. Other disagreements get flagged. The quote has to make sense. It has to read like the clause it claims to be, and boilerplate gets thrown out. Page references get repaired, and when a file has several documents, the newest one wins. Mock-up with fictitious data 6. Escalate only when something looks wrong. Any real flag sends the file to gpt-5.5, also in Foundry, with the same pages and prompt. The first answer is kept for audit.\n7. Then a person looks. If it\u0026rsquo;s still flagged, it goes in a report with a link to the PDF, the page, and the quoted clause.\nMock-up with fictitious data 8. File it. We match the tenant and the property\u0026rsquo;s SharePoint site, then copy the file into the Lease folder for that tenant\u0026rsquo;s apartment. The tool never overwrites, never creates folders, and never deletes originals. Everything else goes to review with a reason.\nWhat It Cost # Tesseract runs locally, so the cost is mostly the AI. These are estimates at list price, not invoices, scaled up to show what it would take at 10,000 files:\nApproach Cost per file At 10,000 files gpt-5-mini on everything about $0.01 about $100 Stronger model on every file about $0.05 to $0.11 about $500 to $1,100 Frontier model on every file about $0.53 about $5,300 The stronger model is a range because these are big files. Only flagged files get escalated, so a real run lands much closer to the first row than the second. One property with 525 files came in at about $4 at the base tier. Time ran about 0.7 minutes per file at the median, with sites running at the same time.\nEstimated at list price What We Learned # Stronger models don\u0026rsquo;t always do better. There isn\u0026rsquo;t a lot of thinking to do here. The model is looking for keywords and specific pieces of information, so with a good prompt, a mini model can find them for far less. After our checks, the cheap model got 47 of 49 hard files right. gpt-5.5 got 46, using more than twice the tokens.\nCategorize first, then go up a model. Work out what kind of document it is. If a check fails, a stronger model reads the same pages to see if there\u0026rsquo;s anything the lighter one didn\u0026rsquo;t pick up.\nIt comes down to repetition. Run a batch, check it by hand, refine. Then do it again. We refined the process for about two weeks to get it as close as possible.\nDon\u0026rsquo;t trust the text in a scan. Scanners embed their own text, and it can be garbled. It even fooled one of our checks, which reused that text and outvoted a correct reading. Now we judge pages by their content and run OCR on an image of the page.\nThe most expensive model found nothing new. A frontier model cost about $24 for 44 files. It found two items we\u0026rsquo;d missed, and both were already in our simple text scan.\nOne test set proves little. The same cheap model looked terrible on one set of hard cases and best on another.\nVision models drop zeros. A typed \u0026ldquo;10 00\u0026rdquo; can read as 100. Now Tesseract settles the digits.\nStamp the page number on the image. In big batches, the model\u0026rsquo;s page references were off by four or five. Stamping the number on each page fixed it.\nYour prompt can leak into the answer. A model added \u0026ldquo;or 5%\u0026rdquo; straight from the prompt\u0026rsquo;s own example. Now a percentage needs evidence on the page.\nWhat\u0026rsquo;s Still Hard # Packets with several leases on different terms stay ambiguous, because nothing finds where one document ends and the next begins. Unusual wording still needs a person. We measured accuracy closely on one property, not the whole archive. Wrapping Up # We processed about 25,000 files, and about 30% of them still needed someone to read them. That\u0026rsquo;s still a lot of reading, but it\u0026rsquo;s a significant reduction from having people go through all of it.\nIt couldn\u0026rsquo;t really get much closer than that, because the files have so much variety. Scan quality varies, some data is missing, and some of it is just unreadable. And every extra page you send to the model costs tokens, so you\u0026rsquo;re always balancing how much you send against what you get back.\nIf you have a pile like this, don\u0026rsquo;t start by shopping for the strongest model. Start with a cheap one, write checks that catch its mistakes, and only pay more when a check fails.\n","date":"Nov 5, 2026","image":"/images/archive-lease-extraction-giant-packets/archive-lease-extraction-giant-packets-hero_hu_52ac90e41485d5b4.webp","permalink":"/2026/11/05/archive-lease-extraction-giant-packets/","section":"Post","summary":"Giant scanned packets of leases, rental agreements, cancellations and letters. How we pulled the files from SharePoint, ran OCR on a local machine, read them …","title":"Finding Lease Data in Giant Scanned Packets","type":"posts"},{"content":"People ask me for the same things over and over.\nOver my career I\u0026rsquo;ve built a lot of SharePoint web parts, and the same requests keep coming back. One of the things I really wanted to deliver this year is a collection: the web parts I\u0026rsquo;ve already built, and the ones people keep asking for, all in one place.\nSo, heads up: web parts are coming.\nTwo Are Already Out # The first two are free on GitHub:\nCompany Store: an internal shop where staff browse company merch and place orders. Onboarding Checklist: a first-month checklist for new starters, with progress their managers can see. Next up is Kaizen Board, where staff share ideas to make work better, vote on them, and follow each one through to done.\nWhy Not Just Ask AI to Build One? # I know. With AI, anybody can build an app now, and it\u0026rsquo;s super easy.\nThe trouble is what you get. A lot of it is flashy on the surface, but the plumbing\u0026rsquo;s no good. And AI is not great at user interface (UI) work, so if the person driving it doesn\u0026rsquo;t have those skills either, it shows.\nI have a UI and design background, and that\u0026rsquo;s the part I won\u0026rsquo;t hand off. When I make an app, I test it myself:\nDoes every button work? Can you click back? Is the navigation easy? Are the fonts right? Is the spacing right? The padding? Are the colors right? I go over them meticulously. It\u0026rsquo;s not exciting work, but it\u0026rsquo;s the difference between something that looks good and something that also works well.\nI\u0026rsquo;m Not Working Alone Anymore # Here\u0026rsquo;s what AI does for me. It lets me move faster, and I\u0026rsquo;m already very busy, so that matters. It helps me develop better patterns because it\u0026rsquo;s good at keeping me on task. It helps me write test cases and check the code for defects, and it helps with end-to-end testing, which means running through the whole app the way a person would.\nSo it\u0026rsquo;s not just me anymore. I have a system in place to make sure these web parts actually work.\nWhy That Matters to You # Giving away a web part is the easy part. Supporting it is what\u0026rsquo;s hard, especially for one busy person.\nThat\u0026rsquo;s really why I built the system. If the testing is solid before a web part goes out, I can give it to people and support it without losing my mind.\nTake a look at the Projects page to see what\u0026rsquo;s out so far. And if there\u0026rsquo;s a web part you keep wishing someone would build, tell me. It might already be on the list.\n","date":"Nov 5, 2026","image":"/images/web-parts-are-coming/web-parts-are-coming-hero_hu_4e4dfa58a6626fe8.webp","permalink":"/2026/11/05/web-parts-are-coming/","section":"Post","summary":"I’m pulling together the SharePoint web parts I’ve built over my career, plus the ones people keep asking for. Here’s what’s coming, and how AI lets me build …","title":"Web Parts Are Coming","type":"posts"},{"content":"I drove up to CollabDays New England with the intention of just being there. No venue to worry about, no sponsors to chase, no coffee order to get wrong. I just went.\nIt\u0026rsquo;s one of my longtime favorite events. I\u0026rsquo;ve been going ever since they\u0026rsquo;ve been having it, and that has to be about 15 years now. It\u0026rsquo;s a well-oiled machine, really well put together, with a tremendous community up there. You get top-tier community speakers and a sprinkling of Microsoft people and Most Valuable Professionals (MVPs). The amount of brain power in that building that day was mind-boggling.\nIt has changed organizers and locations over the years. These days it\u0026rsquo;s run by Julie Turner, Marc Anderson, Derek Cash-Peterson, and Chris Bortlik, at the Microsoft office in Burlington, Massachusetts, which is a beautiful office. Chris, who runs the Microsoft Innovation Hub there, does a wonderful job helping out with this event, and he\u0026rsquo;s really involved. It\u0026rsquo;s amazing to see that kind of commitment from somebody at Microsoft. They show up early, get the day set up, and really tee it up right. That\u0026rsquo;s a whole other blog post.\nJulie Turner hosting the panel with Richard Harbridge, Chris Bortlik, Susan Hanley, Heather Severino, Anna Bordioug, and David Warner Nobody Asked Me To # I\u0026rsquo;m a board member of the Microsoft Global Community Initiative (MGCI), but I\u0026rsquo;m not an official Microsoft regional lead. Nobody assigned me a territory. I just go to almost everything in the area that I can drive to.\nWhen I show up, I\u0026rsquo;m representing myself: a person who deals with the same technology and has the same problems as everybody else. I just happen to be a consultant by day who develops Community Days at night, and I\u0026rsquo;m an event organizer myself.\nNew York, New Jersey, and Pennsylvania are home turf, because I\u0026rsquo;m from the tri-state area. I always go down to DC. I go up to New England, and I\u0026rsquo;ll drive down to Virginia Beach. If it\u0026rsquo;s four or five hours away, no problem.\nSometimes I go even further. Last year I went to Tennessee, and in the past I\u0026rsquo;ve been to Kansas, New Hampshire, Georgia, North Carolina, and California. And yes, all on my own dime.\nYou might think that\u0026rsquo;s crazy, but for me, it\u0026rsquo;s an investment in my career.\nNo Pressure # I run events too, so I know what that morning feels like. When it\u0026rsquo;s your event, you barely see it. You spend the day solving problems, and then it\u0026rsquo;s over.\nWhen it\u0026rsquo;s someone else\u0026rsquo;s event, I get to enjoy it. Sometimes I help out. Sometimes I just show up.\nWhat I Actually Do There # I might sit in one or two sessions. Most of the time I\u0026rsquo;m just working, either on client work or Community Days stuff.\nWhen I\u0026rsquo;m not working, I\u0026rsquo;m talking to people: attendees, sponsors, and the organizers. I watch how the event is run and how well it\u0026rsquo;s working. If I have a tip that might help, I offer it, and I\u0026rsquo;m happy to help out after hours too.\nSucheta Gawade, Chrissy Geluk, and me: the CommunityDays reviewers team Me and Jared Matfess Supporting Other Events # I love supporting other events, and a lot of that comes down to pointing people in the right direction:\nTooling. If an organizer needs tools, I\u0026rsquo;ll tell them where to look. Connections. I\u0026rsquo;ll connect them with the resources and people who can help. Sponsors. If they\u0026rsquo;re looking for sponsors, I\u0026rsquo;ll introduce them to people I think would be a perfect fit for that area. Why I Keep Showing Up # Running a community event can feel lonely. You put in months of work, and you don\u0026rsquo;t always know if anyone outside your own team notices.\nSo I show up, and most of the time I show up a day early. Whether I\u0026rsquo;m driving in or flying in, if I have time, I\u0026rsquo;ll see if they need help setting up and offer to do whatever they need.\nI\u0026rsquo;ll often volunteer during the day too: checking people in for a couple of hours, helping set up lunch, cleaning up, whatever it is. Sometimes the event is very well put together and they have a lot of people, so I just enjoy the day, which is also fantastic.\nAnd I stay late. I won\u0026rsquo;t just fly in and fly home. I hang out and get to know people. We go to dinner, sit around the hotel and chat, or get coffee. Sometimes even ice cream.\nThat extra time connecting with people is super valuable to me. I work from home and don\u0026rsquo;t go to an office, so unless it\u0026rsquo;s online, I just don\u0026rsquo;t see people.\nMe, Ed Williams, Rachel Spannuth, Rachel Sullivan, and Karinne Bessette I\u0026rsquo;m there to show support to other organizers. I want them to see, in person, that the community and I are there to help.\nIf You See Me, Come Say Hello # If I\u0026rsquo;m at an event and you see me there, come say hello. I\u0026rsquo;m putting this out there because I\u0026rsquo;m very easy to talk to and super approachable. You are not bothering me.\nI absolutely enjoy talking to everybody, connecting with people on LinkedIn, adding people to my network, helping people find jobs, and helping people learn new skills.\nSo walk right up. I\u0026rsquo;d love to hear what you\u0026rsquo;re working on.\nLinks # CollabDays New England 2026: this year\u0026rsquo;s event CollabDays New England: the event\u0026rsquo;s official site CommunityDays.org: find an event near you Microsoft Global Community Initiative: the MGCI board and advisors ","date":"Oct 19, 2026","image":"/images/showing-up-community-events/showing-up-community-events-hero_hu_bc95b93829480b1.webp","permalink":"/2026/10/19/showing-up-community-events/","section":"Post","summary":"I’m not a Microsoft regional lead, but if a community event is a few hours’ drive away, I’m probably there. What I do when I show up, and why I think it matters …","title":"Just Showing Up: Why I Drive to Community Events I'm Not Running","type":"posts"},{"content":"There\u0026rsquo;s a moment at every event when it stops feeling like work. At M365 Community Days NYC this year, it came early.\nOn July 31st, more than 240 people checked in at Microsoft\u0026rsquo;s office at 11 Times Square for a full day of Microsoft 365 (M365), Copilot, and AI. More than forty sessions, two panels, and rooms that were full from the first session at 9:30 right through Heather Cook\u0026rsquo;s closing keynote.\nA full room at Microsoft\u0026rsquo;s office at 11 Times Square Learning From the Person Next to You # You can watch a recorded session on your own. What you can\u0026rsquo;t do is turn to the person next to you and ask how they got the same thing past their own security team. That\u0026rsquo;s what these days are for.\nKishore Bitra from the City of Baltimore showed a resident services agent built in Copilot Studio, and the questions kept coming long after the session ended. One attendee called the Women in Tech panel the highlight of the day. Author Jared Matfess signed books.\nJared Matfess signing books for his fans Heather Cook closed it out with the keynote, and got a room full of IT people to quietly write postcards to their future selves.\nHeather Cook and her keynote The team from RealActivity called it \u0026ldquo;a volunteer-led event tighter than most paid conferences.\u0026rdquo; I\u0026rsquo;ll take that.\nThe Game That Gets People Talking # If you were there, you played GooseChase. It\u0026rsquo;s a scavenger hunt that runs on your phone all day, and the missions are simple and a little silly: take a photo with a group of people drinking coffee, find someone wearing the same color shirt as you, grab a picture with a speaker. Every one of them means walking up to someone you don\u0026rsquo;t know.\nThat\u0026rsquo;s the whole point. It gives people a reason to say hello, and it\u0026rsquo;s a fan favorite every year.\nThe winner of the Golden Goose Award from GooseChase, with me and Rajaniesh Kaushikk Volunteer Run, Start to Finish # Nobody gets paid to run M365 NYC, and the prep starts long before the doors open. Every year it begins with a car packed to the roof with snacks, prizes, and equipment for the drive into the city. Pradeep Raja recruited and led 16 volunteers, some of them at their very first conference. Sucheta Gawade built the GooseChase game mission by mission, chased every speaker and sponsor for content, and was still adding missions the morning of the event. Diego Domingos da Silva ran all of our social media from a distance, made a spotlight video for every speaker, learned new tools to do it, and went way past anything we asked for. It landed: speakers thanked him by name in their own posts, and if you run events, his write-up is worth your time. Sucheta and Diego, I\u0026rsquo;m very grateful.\nPacked to the brim: organizer problems Pradeep Raja and our rockstar team of volunteers Sucheta Gawade receiving her Queen Goose Award, with Peter Ward on the left Thank You # Congratulations to the whole team: Pradeep Raja, Sucheta Gawade, Diego Domingos da Silva, Pooja Rajpal, Stacy Burris, Claire Hynes, Amie Seisay, Jason Rivera, Amanda Coe, Himanshu Goil, Manpreet Singh, Rajaniesh Kaushikk, and Peter Ward.\nThank you to our headline sponsor Devicie, and to AvePoint, Cambay Solutions, Orchestry, Seisay IT Solutions, Cloudwell, SoHo Dragon, Skillstinget, AI Maitri, and the Tri-State Office 365 User Group. Thank you to Microsoft for opening the doors at Times Square again, and to every speaker who gave up a Friday to teach what they know.\nNot everything went right. The lunch line and audio/video help are already on next year\u0026rsquo;s list.\nImpromptu group shot at the end of the day with some of our attendees and speakers Why We Keep Doing This # You get to watch a room fill up. You see someone new find their feet by lunchtime. And Pradeep, after a long day running volunteers, has already asked to take on sponsors next year.\nThat\u0026rsquo;s how community events last. New people get pulled in, get a real job to do, and find out they\u0026rsquo;re good at it. If you organize anything, ask someone new to help.\nSee you next year.\nLinks # M365 NYC: the official site, with the full 2026 schedule Creating community before the event: Lessons from M365 NYC: Diego\u0026rsquo;s write-up CommunityDays.org: find an event near you ","date":"Oct 16, 2026","image":"/images/m365-nyc-2026-recap/m365-nyc-2026-recap-hero_hu_69ee32d4c301c546.webp","permalink":"/2026/10/15/m365-nyc-2026-recap/","section":"Post","summary":"A short look back at M365 Community Days NYC 2026: full rooms, a scavenger hunt that gets strangers talking, and the volunteers who made it all happen.","title":"M365 NYC 2026: Learning From the Person Next to You","type":"posts"},{"content":"Scripts: github.com/tom-daly/demos/grant-azure-function-email-access\nYou have an Azure Function that needs to send email. Booking confirmations, alerts, a nightly digest.\nA service like SendGrid can do it, and once you\u0026rsquo;ve set up your domain\u0026rsquo;s authentication it can send as your domain too. But if you already have Microsoft 365, there\u0026rsquo;s a simpler route: send through a mailbox you already own, using Microsoft Graph. No extra service, no extra account, and replies land in a mailbox your team can open.\nSo you look up the permission. You find Mail.Send. You grant it. It works.\nThat grant is unscoped. The Function App can now send as any mailbox in the tenant, not only the one you had in mind.\nThe Bit Nobody Mentions # App-only doesn\u0026rsquo;t mean narrowly scoped. It means the application operates without a signed-in user.\nAnd broad Graph permissions really are broad. Files.Read.All gives the application access to all files represented by that permission. An Entra-granted Mail.Send application permission authorizes it to send mail as any user.\nNot every application permission works this way — Graph does have scoped ones, such as Sites.Selected and Files.SelectedOperations.Selected, where you nominate the specific resources afterwards. Mail is not among them.\nThere\u0026rsquo;s no \u0026ldquo;just this one mailbox\u0026rdquo; option on the Entra consent screen. Mailbox scoping is handled in Exchange Online — which is why it\u0026rsquo;s so easy to miss.\nThere are two ways this may be configured:\nThe legacy model grants Mail.Send in Entra and then restricts it using an Exchange application access policy. The recommended model grants a scoped Application Mail.Send role through Exchange Application RBAC, without retaining the tenant-wide Entra permission. That last point matters: Exchange RBAC does not override or narrow an existing Entra grant. The permissions are additive. If tenant-wide Mail.Send remains in Entra, the application can still send outside the Exchange RBAC scope.\nThe short version: if an app holds tenant-wide Mail.Send in Entra and no Exchange application access policy restricts it, its sending is open — anything that can authenticate as that app can send as any mailbox in the tenant. A scoped Exchange RBAC assignment does not close that while the broad Entra grant remains.\nWhat You\u0026rsquo;re Building # Three pieces. The first two take about a minute each; the third is where the work is.\nA shared mailbox — notifications@yourdomain.com. Usually no licence, nobody signs in, and mail from it is properly yours: your domain, your reputation. Two caveats: a shared mailbox does need a licence once it passes 50 GB, or if you want a bigger archive, put it on litigation hold, or apply certain retention features. And DKIM signing isn\u0026rsquo;t automatic on a custom domain — switch it on for the domain, or your mail is signed as onmicrosoft.com. A managed identity on the Function App — one toggle in the portal. An identity with no password. No application secret to store or rotate. A permission that covers only that mailbox — the rest of this post. Two Ways to Scope It # Where the permission lives Ever tenant-wide? Status RBAC for Applications Exchange Online only No Current guidance Application access policy Entra ID, restricted afterwards by Exchange Yes, for a while Legacy Side by side, the difference is where the permission is created — and therefore whether a tenant-wide grant ever exists at all.\nThe Current Way: RBAC for Applications # Exchange has its own role-based access control for applications. Nothing is granted in Entra ID. You register the app inside Exchange, describe which mailboxes it may touch, and give it a role limited to those.\nThree commands. Two IDs come from the Function App\u0026rsquo;s managed identity: $ObjectId is its service principal object ID (the \u0026ldquo;Object (principal) ID\u0026rdquo; on the Function App\u0026rsquo;s Identity page), and $AppId is its application (client) ID, shown on the matching entry under Enterprise applications in Entra.\n# 1. Tell Exchange this app exists. New-ServicePrincipal -AppId $AppId -ObjectId $ObjectId -DisplayName \u0026#34;Notifications sender\u0026#34; # 2. Describe which mailboxes are in play. Here: one. New-ManagementScope -Name \u0026#34;Notifications mailbox\u0026#34; -RecipientRestrictionFilter \u0026#34;Alias -eq \u0026#39;notifications\u0026#39;\u0026#34; # 3. Grant Mail.Send, already limited to that scope. New-ManagementRoleAssignment -App $AppId -Role \u0026#34;Application Mail.Send\u0026#34; -CustomResourceScope \u0026#34;Notifications mailbox\u0026#34; Then prove it took:\nTest-ServicePrincipalAuthorization -Identity $AppId -Resource notifications@contoso.com RoleName InScope AllowedResourceScope -------- ------- -------------------- Application Mail.Send True Notifications mailbox That tells you the Exchange side is right. It does not tell you what the app can actually do. It reads Exchange\u0026rsquo;s own roles and nothing else, and it evaluates them there and then rather than through the token and caching a real request goes through. An old Mail.Send sitting in Entra wouldn\u0026rsquo;t appear here at all.\nSo check the other place too:\n# Expect nothing back. $graphSp = Get-MgServicePrincipal -Filter \u0026#34;appId eq \u0026#39;00000003-0000-0000-c000-000000000000\u0026#39;\u0026#34; $role = ($graphSp.AppRoles | Where-Object Value -eq \u0026#34;Mail.Send\u0026#34;).Id Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $ObjectId -All | Where-Object { $_.AppRoleId -eq $role } Then check it for real: get a token as the app and call Graph twice — once from the mailbox you scoped to, which should send, and once from a mailbox you didn\u0026rsquo;t, which should come back ErrorAccessDenied. Only the second call proves the boundary is actually there.\nGive it time before you trust that result. Microsoft says Exchange caches app permissions for anywhere from 30 minutes to two hours, and the test cmdlet above skips that cache. So a real send made straight after setup can still reflect the old permissions. If the out-of-scope send succeeds in the first couple of hours, wait and try again before concluding anything.\nThree commands and two checks. My full script is about 200, and nearly all the difference is the four things below.\nGlobal Administrator wasn\u0026rsquo;t enough for me # This cost me an afternoon.\nAssigning an application role in Exchange needs what Exchange calls a delegating assignment for that role. Microsoft puts it on the Organization Management role group. On paper, Global Administrator and Exchange Administrator in Entra both map to that group, and Microsoft\u0026rsquo;s article names Exchange Administrator as the Entra role you need.\nIn my tenant it didn\u0026rsquo;t work out that way. As Global Admin I got through every other step. Service principal, fine. Scope, fine. Then the last command refused, and the error talked about delegating role assignments — which doesn\u0026rsquo;t obviously mean \u0026ldquo;join a group\u0026rdquo;. What fixed it was adding my account to Organization Management directly:\nConnect-ExchangeOnline -UserPrincipalName you@contoso.com Add-RoleGroupMember -Identity \u0026#39;Organization Management\u0026#39; -Member you@contoso.com Disconnect-ExchangeOnline -Confirm:$false Connect-ExchangeOnline -UserPrincipalName you@contoso.com Connecting twice isn\u0026rsquo;t a typo. An open Exchange session won\u0026rsquo;t see the new membership. Give it a few minutes, too.\nJoining that group is the quick way through. If handing someone Organization Management is more than you want, a custom Exchange role group carrying the delegating assignment does the same job with less attached to it.\nMy script checks for the permission before creating anything, so you find out in the first two seconds rather than the last.\nUse Alias, not the email address # Look at the scope filter again:\n-RecipientRestrictionFilter \u0026#34;Alias -eq \u0026#39;notifications\u0026#39;\u0026#34; The obvious version is PrimarySmtpAddress -eq 'notifications@contoso.com'. Microsoft\u0026rsquo;s own docs say don\u0026rsquo;t. A filter on the primary address also searches every proxy address on the recipient. So if the address you typed turns out to be a secondary address on some other mailbox, that other mailbox is the one your scope covers.\nIt quietly moves a security boundary and never shows you an error. Filter on the alias, then check the scope picks up exactly the mailbox you meant and nothing else:\nGet-Recipient -RecipientPreviewFilter (Get-ManagementScope \u0026#34;Notifications mailbox\u0026#34;).RecipientFilter Exchange fails without failing # This one isn\u0026rsquo;t specific to mail, and it\u0026rsquo;s the one I\u0026rsquo;d watch for.\nIn my runs, some Exchange Online cmdlets reported a refusal as a non-terminating error and carried on, even with $ErrorActionPreference = \u0026quot;Stop\u0026quot; set at the top of the script. Normally that setting turns these into hard stops. My best guess is that the Exchange cmdlets live inside a module, and a preference set in your script doesn\u0026rsquo;t always reach in there. Whatever the cause, the refusal printed in red, PowerShell carried on, and the script reached the end and printed my success message.\nYou get a green tick for something that didn\u0026rsquo;t happen.\nSo don\u0026rsquo;t trust the command — check afterwards. Below, the command is deliberately allowed to report and carry on, and the check decides whether it worked. The check looks for the right role and the right scope, so an unrelated assignment on the same scope doesn\u0026rsquo;t count:\n$assigned = { Get-ManagementRoleAssignment -RoleAssignee $AppId -ErrorAction SilentlyContinue | Where-Object { $_.Role -eq \u0026#34;Application Mail.Send\u0026#34; -and $_.CustomResourceScope -eq $Scope } } New-ManagementRoleAssignment -App $AppId -Role \u0026#34;Application Mail.Send\u0026#34; ` -CustomResourceScope $Scope -ErrorAction Continue | Out-Null if (-not (\u0026amp; $assigned)) { # It didn\u0026#39;t take. Say so, loudly. } Every step in both scripts is checked after it runs, because whether the permission actually got created isn\u0026rsquo;t something to take on trust.\nConnect-ExchangeOnline may not connect where you asked # It can reuse a cached session belonging to a different tenant. If you work across several — consultants, this is you — you can run a script aimed at one customer and create objects in another.\nThat happened to me.\nWith more than one connection open, it isn\u0026rsquo;t obvious which one your commands will use, so the safe answer is to insist on exactly one, in the right tenant:\n$ci = @(Get-ConnectionInformation | Where-Object State -eq \u0026#39;Connected\u0026#39;) if ($ci.Count -ne 1 -or $ci[0].TenantId -ne $Tenant) { # Stop. Nothing has been created yet. } Both scripts take the tenant ID as a required parameter and refuse to move until the session matches.\nOne smaller thing: Exchange won\u0026rsquo;t create the role assignment until the tenant has been customized. Most were, years ago, as a side effect of something else. If not, you need Enable-OrganizationCustomization. It\u0026rsquo;s harmless but irreversible, so my script names the command instead of running it for you.\nThe Older Way: Application Access Policies # If you\u0026rsquo;ve done this before, you did it this way: grant Mail.Send in Entra, then restrict it with an Exchange application access policy. Microsoft now marks it legacy, but it works and plenty of tenants run on it.\nIt\u0026rsquo;s still worth understanding, because the order you do it in is a security decision.\nBetween granting Mail.Send and creating the policy, the app\u0026rsquo;s sending is wide open. Grant first, then discover you\u0026rsquo;re not an Exchange admin — or your session drops, or you get pulled into a meeting — and that window stays open as long as it stays open.\nSo: policy first, permission last. And check you\u0026rsquo;re allowed to create the policy before granting anything.\n# The restriction, first. if (-not (Get-Command New-ApplicationAccessPolicy -ErrorAction SilentlyContinue)) { throw \u0026#34;Not an Exchange administrator - stopping before anything is granted.\u0026#34; } New-ApplicationAccessPolicy -AppId $miSp.AppId ` -PolicyScopeGroupId $grp.PrimarySmtpAddress ` -AccessRight RestrictAccess ` -Description \u0026#34;Restrict mail to the service mailbox\u0026#34; # The grant, last. Add-GraphAppRole \u0026#34;Mail.Send\u0026#34; Order alone isn\u0026rsquo;t the whole story, though. A new application access policy isn\u0026rsquo;t in force the moment the cmdlet returns — Microsoft says it can take more than an hour to affect Graph requests. Grant Mail.Send straight after creating the policy, as the snippet above does, and the app can hold an unrestricted grant for that long before Exchange starts refusing anything.\nSo create the policy, give it time, then grant. Once it\u0026rsquo;s granted, test a mailbox outside the group and confirm it\u0026rsquo;s refused.\nIf you\u0026rsquo;d rather not depend on timing at all, use RBAC for Applications instead, where no tenant-wide grant is ever created in the first place.\nFor a shared mailbox, use a mail-enabled security group # You can point a policy at an ordinary user mailbox, but not at a shared one. Shared mailboxes don\u0026rsquo;t count as security principals, so you get:\nThe identity of the policy scope is not a security principal. So you make a mail-enabled security group whose only member is the mailbox:\nNew-DistributionGroup -Name \u0026#34;notifications-scope\u0026#34; -Type Security ` -Members notifications@contoso.com ` -Notes \u0026#34;Application access policy scope\u0026#34; A group of one feels like overkill, but that\u0026rsquo;s the shape the cmdlet takes. It also means the restriction is only as tight as the group\u0026rsquo;s membership — anyone who can add a member widens what your app may send as. Worth deciding who owns it.\nReal requests lag behind the test # Create the policy, test a mailbox that should be blocked, and it may still say Granted. Or the test may look right while real Graph requests still behave the old way — Microsoft says those can lag by more than an hour, even when the test cmdlet shows the result you expect.\nSo don\u0026rsquo;t build any fixed wait into your expectations. My script checks the service mailbox is allowed, then prints a note telling you to come back later and confirm, with a real send, that a mailbox outside the group is refused. It doesn\u0026rsquo;t treat the policy as in force until something has actually been refused.\nDon\u0026rsquo;t Leave Both in Place # This is the additive behaviour from the top of the post, and it\u0026rsquo;s where migrations come unstuck. Set up Exchange RBAC on an app that still holds a tenant-wide Mail.Send grant from years ago, and your careful one-mailbox scope changes nothing — the old grant leaves sending open across the tenant.\nRunning both side by side for a short time is fine. Microsoft\u0026rsquo;s own migration steps do exactly that. The mistake is stopping there. Once the RBAC assignment is in place, remove the old Entra grant, then remove the old access policy.\nFind the Entra grant and remove it:\n$graphSp = Get-MgServicePrincipal -Filter \u0026#34;appId eq \u0026#39;00000003-0000-0000-c000-000000000000\u0026#39;\u0026#34; $role = ($graphSp.AppRoles | Where-Object Value -eq \u0026#34;Mail.Send\u0026#34;).Id Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $miObjectId -All | Where-Object { $_.AppRoleId -eq $role } | ForEach-Object { Remove-MgServicePrincipalAppRoleAssignment ` -ServicePrincipalId $miObjectId -AppRoleAssignmentId $_.Id } The Easy Part: Actually Sending # After all that, the app code is unremarkable — no secret, no connection string, no client ID and password sitting in configuration.\nconst mailbox = process.env.EMAIL_FROM_MAILBOX; // notifications@contoso.com if (!mailbox) { context.log(\u0026#39;[email] skipped - EMAIL_FROM_MAILBOX is not set\u0026#39;); return; } const credential = new DefaultAzureCredential(); const token = await credential.getToken(\u0026#39;https://graph.microsoft.com/.default\u0026#39;); const response = await fetch( `https://graph.microsoft.com/v1.0/users/${encodeURIComponent(mailbox)}/sendMail`, { method: \u0026#39;POST\u0026#39;, headers: { Authorization: `Bearer ${token.token}`, \u0026#39;Content-Type\u0026#39;: \u0026#39;application/json\u0026#39;, }, body: JSON.stringify({ message: { subject: \u0026#39;Your booking is confirmed\u0026#39;, body: { contentType: \u0026#39;HTML\u0026#39;, content: html }, toRecipients: [{ emailAddress: { address: \u0026#39;guest@example.com\u0026#39; } }], }, saveToSentItems: false, }), }, ); if (!response.ok) { throw new Error(`[email] Graph refused the send: ${response.status} ${await response.text()}`); } Three things in there are deliberate.\nThe response is checked. fetch doesn\u0026rsquo;t throw when Graph says no — a 403 because the scope isn\u0026rsquo;t right, or a 429 because you\u0026rsquo;re being throttled, comes back as an ordinary response with ok: false. Ignore it and the app carries on and logs a successful send for a message that never went. Note also that a good send returns 202 Accepted, which means Graph has taken the message, not that anybody received it — bounces turn up later in the mailbox and in message trace.\nsaveToSentItems: false. The mailbox belongs to the app, not a person. A copy of every notification ever sent piling up in Sent Items is a mailbox somebody has to empty. Log enough to answer \u0026ldquo;did we send it, and to whom\u0026rdquo; — recipient, subject, time, what Graph said — and leave the message body out unless you\u0026rsquo;ve decided it\u0026rsquo;s safe to keep.\nThe mailbox address is a setting, not a constant. I leave it empty on purpose in early deployments. That\u0026rsquo;s the check at the top of the sample: while the setting is empty, every send is skipped and logs why — so the whole path can be built, deployed and watched working before the mailbox exists or IT has run the script. The day it\u0026rsquo;s created, one setting turns sending on. No deployment.\nOn the project this came from, the client\u0026rsquo;s IT team ran the permission script weeks after the code was finished, and turning it on was one setting.\nWhere That Leaves You # An Entra-granted Mail.Send covers every mailbox. Scoping happens in Exchange, not Entra. Grant the scoped role through Exchange RBAC and no tenant-wide grant is ever created — but only if you don\u0026rsquo;t also leave one sitting in Entra, because the two add together rather than the narrower one winning.\nBoth scripts are at github.com/tom-daly/demos/grant-azure-function-email-access — the RBAC one, the legacy one, and the TypeScript sending code. Most of their length is checks and error messages rather than the three commands that do the work, because they get handed to a client\u0026rsquo;s IT team to run on a tenant I\u0026rsquo;m not sitting in front of.\nIf you have apps in your tenant holding Mail.Send, check both places: the Entra app-role assignments, and whatever Exchange has scoped. A scope in one doesn\u0026rsquo;t undo a grant in the other.\n","date":"Sep 1, 2026","image":"/images/azure-function-send-email-one-mailbox/azure-function-send-email-one-mailbox-hero_hu_9be58f5fed2a5527.webp","permalink":"/2026/09/01/azure-function-send-email-one-mailbox/","section":"Post","summary":"Mail.Send lets an Azure Function send as any mailbox in your tenant. How to scope it to one mailbox with Exchange, and the traps along the way.","title":"Letting an Azure Function Send Email as One Mailbox — and Only One","type":"posts"},{"content":"A property management company had thousands of lease documents coming in, and every one of them needed to end up in the right SharePoint folder for the right owner, tenant, and property. Someone was doing this by hand: open the PDF, read the first page, work out who it belonged to, and click down through the folders to file it.\nI wanted to build something that could do the reading and the filing automatically. Getting it working on a few documents was the easy part. Most of what I learned came after real documents started flowing through it.\nHow It Works # The system is a chain of Azure Functions connected by Azure Storage Queues. Each stage does one job and passes a message to the next. If a stage fails, the queue retries it.\nA document arrives. It\u0026rsquo;s uploaded to a SharePoint intake library. A webhook fires. A Microsoft Graph subscription tells a Function that something new showed up. AI reads it. Microsoft Foundry pulls out the tenant, owner, addresses, lease type, and start date. Search finds the destination. The Graph Search API looks for the right site and folder. The document gets filed. It moves to the folder, with the extracted details written to SharePoint columns. A simplified view of the pipeline If required details are missing, or the search finds no match or more than one, the document goes to a Manual Review folder instead of disappearing.\nWhat the AI Gets # The model gets two things: an image of the first page, and text from the first ten pages, read by Azure Document Intelligence.\nMany of these leases are scanned, and the text layer in a scanned PDF might be clean, garbage, or missing. The image lets the model look at the page the way a person would, and the text catches details that sit further in.\nIt returns structured JSON, and that drives everything downstream.\nMock-up with fictitious data The Good # When it works, a lease gets dropped in and lands in the right folder with nobody touching it.\nThe columns turned out to be a big deal on their own. Finding a lease used to mean clicking through a folder tree and hoping it had been filed correctly. Now you can search, filter, and sort by tenant, owner, lease type, and start date.\nMock-up with fictitious data The queues were also the right call. If one stage slows down, messages stack up and wait, and the other stages keep going.\nIt\u0026rsquo;s cheap to run, too. On gpt-5-mini, the AI works out to about a penny a document.\nThe Surprises # The pipeline kept triggering itself. Writing the extracted details back to a document counts as a change, and a change fires the webhook. One upload was setting off more than eleven runs within a few seconds. The fix was an ETag-conditional update that claims the document first. Only one run can win, and the others get a 412 Precondition Failed and stop.\nThe model ran out of room to answer. I\u0026rsquo;m using a reasoning model, and its reasoning counts against the completion limit. With a 2,000-token cap, it could use it all up on a dense lease page and return empty JSON, which looked exactly like a lease with missing details. These two settings took a test batch from eight of those failures to none:\nprivate const int MaxCompletionTokens = 16384; private const string ReasoningEffort = \u0026#34;low\u0026#34;; Sites weren\u0026rsquo;t named the way I assumed. I was searching by owner name, but the property sites were named after building addresses, so owners with several buildings were missed. The system now learns an address-to-site map.\nFull addresses made matching worse. The documents have the full street, city, state, and ZIP code, but the property list has only the street. Those extra words dropped the fuzzy match score to about 0.5, well under the 0.85 it needs, so good documents went to Manual Review. Now it compares just the street line.\nBe careful what the system learns from. It was saving a new address match before the document had finished moving. One bad route could have misdirected every later document for that building. Now it only learns after the move is validated.\nMoving across drives isn\u0026rsquo;t really a move. Between SharePoint document libraries on different drives, you have to copy the file, check that it arrived, and then delete the original. A file can show up at 0 bytes while it\u0026rsquo;s still copying, so the check has to compare sizes too. The copy runs in the background, so I re-queue the check every 30 seconds, up to 20 times, rather than holding a Function open.\nWebhook subscriptions quietly expire. We set ours to about 30 days. When they lapse, you get no errors and no alerts. Documents just stop being processed. I had to build a timer function that runs on a schedule to renew them.\nMock-up with fictitious data Queue messages have a size limit. A message maxes out at 64KB, which is about 48KB after base64 encoding. Once you\u0026rsquo;re passing extracted details and routing decisions between stages, that fills up. I had to be deliberate about what goes in each message and what the next stage looks up fresh.\nWhat I\u0026rsquo;d Tell You Before You Build One # Manual Review isn\u0026rsquo;t failure. It\u0026rsquo;s the design. Early on, I thought the goal was 100% automatic routing. The real goal is making the easy cases automatic and the hard ones visible. A document in a review folder is much better than one quietly filed in the wrong place.\nWe also built a SharePoint web part for clearing that queue, and it turned out to be really critical. It shows the document next to what the AI found and the suggested folders, so people can do everything within a few clicks instead of interacting with a SharePoint list. It also let us fine-tune exactly what actions they could take.\nMock-up with fictitious data It may never be perfect. Most documents file themselves, and we keep fine-tuning it, but the documents coming in are clearly not ideal in some cases. Scan quality varies, and so do the types of leases, which makes this a never-ending problem. It\u0026rsquo;s getting better, and we can at least categorize most of them.\nDocuments with a single purpose usually work great. When a bunch of things are jammed into one file, it doesn\u0026rsquo;t work out well, and it takes a lot longer to parse. We use a different technique for those multi-purpose files, which I won\u0026rsquo;t get into here.\nAudit everything. Every stage writes to a SharePoint audit list, with a correlation ID that ties each entry to one document. When something goes wrong, you can trace that document\u0026rsquo;s whole journey and see where it went sideways. This saved me hours of debugging.\nMock-up with fictitious data Know where the data goes. Leases hold personal details, so people will ask. Each document ends up in the customer\u0026rsquo;s SharePoint, in the correct site and tenant folder.\nWrapping Up # The person who used to spend the day filing leases now only handles the cases that land in Manual Review.\nUltimately, this project was extended to take on more initiatives like this one, related to scanning and more complex work. That\u0026rsquo;s probably for another post.\nThe pattern works beyond leases, too. If you can describe what to pull from a document and where it should go, it\u0026rsquo;s a candidate for this kind of pipeline.\n","date":"Aug 18, 2026","image":"/images/azure-ai-document-routing-sharepoint/azure-ai-document-routing-sharepoint-hero_hu_df6b7bc83910f4ff.webp","permalink":"/2026/08/18/azure-ai-document-routing-sharepoint/","section":"Post","summary":"How I used Azure AI to read lease documents and file them in the right SharePoint folder, and the surprises that didn’t show up until real documents started …","title":"Automated Lease Routing with Microsoft Foundry, Azure Functions, and SharePoint","type":"posts"},{"content":"I\u0026rsquo;ve been renewed as a Microsoft MVP for 2026-2027. That makes it my tenth year in the program.\nThe first award came in 2017. A decade later the email still lands the same way — gratitude for the recognition, and knowing the Microsoft product teams value the work I\u0026rsquo;m putting out there for the community.\nI\u0026rsquo;ll admit I\u0026rsquo;m doubly excited this year. Ten years earns the blue puck, and it goes on top of the stack.\nTen Years, Briefly # Back then my contributions were blog posts, forum answers, and SharePoint Saturdays. Now it\u0026rsquo;s running the platform people use to find community events worldwide, running events across New York, New Jersey, and Pennsylvania, mentoring, and still writing about the problems I hit at work.\nWhat the Award Is Worth # Two things: access and trust.\nAccess is a direct line to the product teams — feedback that lands somewhere, and seeing where the platform is heading before it ships. That makes my advice better.\nTrust is the other half. People are more confident an event is worth their time when MVPs are running it. That brings in speakers, sponsors, and attendees.\nThis Past Year # Community Days and MGCI # Much the same as last year, which is the point — this work is steady, not flashy. I\u0026rsquo;m still on the board and still running the Tech and Tools group behind CommunityDays.org.\nThe difference is that I\u0026rsquo;ve spent more of the year building that group out than building things myself. Getting other people to contribute, and giving them room to own the work, gets more done than anything I could ship alone.\nI\u0026rsquo;m also still in the room with other event organizers — the office hours, the training sessions, the back-channel questions from someone running their first event. That\u0026rsquo;s where a lot of the useful work actually happens.\nEvents in the Tri-State Area # I\u0026rsquo;m still running events across New York, New Jersey, and Pennsylvania — the venue, the sponsors, the coffee order, and a room filling up with people who took a Friday out of their week to learn something. That part hasn\u0026rsquo;t changed in ten years.\nThis year that meant Experts Live US in October, M365 Philly in May, the AI Community Conference in New York in June, and M365 Community Days NYC at the end of July. Next up is the AI Community Conference in New Jersey on September 25th.\nA Team, and a Non-Profit # The bigger change is that I\u0026rsquo;m not doing it alone. A real team has formed around these events — people who show up every time and own their piece of it. It stopped being something I organize and became something we organize.\nThe NYC crew this year: Rajaniesh Kaushikk, Manpreet Singh, Peter Ward, Pooja Rajpal, Jason Rivera, Amie Seisay, Claire Hynes, Pradeep Raja, Sucheta Gawade, Diego Domingos da Silva, Himanshu Goil, and Stacy Burris.\nThis year we formed a non-profit, Ctrl-Alt-Learn, to sit behind it — cofounded with Amie Seisay and Jason Rivera. Sponsorship money now goes somewhere accountable, the paperwork is out of my personal accounts, and the events can outlast any one of us. That last part is why we did it. Community work is fragile when it runs on one person\u0026rsquo;s calendar.\nHelping Other People Get There # The part I\u0026rsquo;m proudest of is the conversations.\nSomeone wants to speak but doesn\u0026rsquo;t think they have anything worth saying. Someone\u0026rsquo;s stuck in a role and can\u0026rsquo;t see the next step. Someone new doesn\u0026rsquo;t know which events matter or who to talk to. I\u0026rsquo;ve been on the other side of all three, and an honest answer is worth more than anything I could write.\nA few of those people have gone on to earn the MVP award themselves. They earned it — the work was theirs. But knowing where to put your energy and who to meet shortens a path that took me years to find. Watching someone get that email beats getting my own.\nThank You # To Microsoft, to the program managers who support us, and to everyone who has organized alongside me, spoken at an event, or just said thanks — thank you. Ten years happened because of you.\nIf you\u0026rsquo;re thinking about contributing, start small. Pick one:\nVolunteer at an event near you Post or answer a question in an online forum Join one of the many Microsoft community calls Contribute to one of the many open source projects in the Microsoft space That\u0026rsquo;s how all of us started.\n","date":"Jul 31, 2026","image":"/images/renewed-microsoft-mvp-2026/renewed-microsoft-mvp-2026-hero_hu_4282a01836cb456.webp","permalink":"/2026/07/30/renewed-microsoft-mvp-2026-2027/","section":"Post","summary":"Renewed as a Microsoft MVP for 2026-2027 — my tenth year in the program, what it has meant, and why I keep showing up.","title":"Ten Years as a Microsoft MVP","type":"posts"},{"content":" A mom-and-pop camera parts store came to us with a problem. They\u0026rsquo;d been running their inventory consignment tracking on a Power App built on top of SharePoint lists, and it was falling apart. Not in a dramatic way — more like death by a thousand cuts. Slow screens, security workarounds, lists bumping up against row limits, and a UI that fought them every time they needed to add a feature.\nThe app tracked consignment memos — when parts went out on consignment to other dealers, what was sent, what came back, what was still outstanding. It\u0026rsquo;s the kind of workflow that sounds simple until you realize it touches inventory, customers, sales staff, PDF generation, email notifications, and reporting. The Power App had gotten them through the first couple of years, but they\u0026rsquo;d hit a ceiling.\nWe rebuilt it as a custom web application. And the part that made it feasible for a small team on a reasonable budget was AI-assisted development — not as a gimmick, but as a genuine force multiplier for the grunt work.\nWhat Went Wrong with the Power App # Power Apps are great for getting something running quickly. You connect to a SharePoint list or Dataverse table, drag some controls onto a screen, write a few formulas, and you\u0026rsquo;ve got a working app in a day. For simple workflows with a handful of users, that\u0026rsquo;s genuinely powerful.\nBut this app had grown well past that sweet spot. Here\u0026rsquo;s what broke down:\nSpeed # The app was painfully slow. Every screen load involved multiple lookups against SharePoint lists, and Power Apps doesn\u0026rsquo;t give you much control over how or when those queries execute. Users would tap a button and wait several seconds for the next screen to render. For a tool people use dozens of times a day, that friction adds up fast.\nDelegation was a constant headache. Power Apps delegates certain operations to the data source, but many common operations — like certain filter combinations or sorting on calculated fields — can\u0026rsquo;t be delegated. That means the app silently pulls only the first 500 or 2,000 rows and works with that subset, which led to missing records in search results. Users would search for a memo they knew existed and get nothing back.\nThe Large List Problem # SharePoint lists have a 5,000-item list view threshold. Once you cross it, queries that aren\u0026rsquo;t indexed start failing or returning incomplete results. The consignment memo list had blown past this limit months ago. We\u0026rsquo;d added indexes on the most-queried columns, but Power Apps\u0026rsquo; delegation model meant some queries still couldn\u0026rsquo;t take advantage of them.\nThe workarounds were ugly — splitting data across multiple lists, pre-filtering with flows, caching subsets locally. Each workaround added complexity and new failure modes.\nToo Many Controls # Power Apps renders every control on a screen, even ones that aren\u0026rsquo;t visible. As the screens grew more complex — conditional sections, expandable details, dynamic form fields — performance degraded. We\u0026rsquo;d hit screens with 200+ controls, and the app would visibly struggle to render them.\nThe recommended fix is to split screens into smaller pieces, but that creates a disjointed user experience and makes state management even harder. You end up passing data between screens through global variables and collections, which is fragile and hard to debug.\nSecurity Limitations # Power Apps\u0026rsquo; security model is tied to the underlying data source. With SharePoint, that means item-level permissions or breaking inheritance — neither of which scales well. The business needed role-based access: admins see everything, sales staff see only their stores, viewers get read-only access. Implementing this cleanly in Power Apps required a tangle of conditional visibility rules and duplicate screens, and it was never quite right.\nHard to Hand Off # This is the one that doesn\u0026rsquo;t get talked about enough. Power Apps are notoriously difficult to hand off to another developer. There\u0026rsquo;s no proper source control. The \u0026ldquo;code\u0026rdquo; is a mix of Excel-like formulas scattered across control properties. There\u0026rsquo;s no way to do a meaningful code review or diff. Documentation is whatever comments you\u0026rsquo;ve added to your formulas — and let\u0026rsquo;s be honest, most Power App formulas don\u0026rsquo;t have comments.\nWhen the original developer moves on, the next person has to reverse-engineer the app by clicking through every screen and reading every control\u0026rsquo;s property panel. For a complex app, that can take weeks just to understand what it does, let alone modify it safely.\nHard to Extend # Every new feature request felt like a negotiation with the platform. Need to generate a PDF? You\u0026rsquo;re piping data to a Power Automate flow that calls a third-party connector or a custom API. Need to send styled HTML emails on a schedule? Another flow, another connector, another set of failure points. Need a proper grid with sorting, filtering, and inline editing? You\u0026rsquo;re stacking galleries and buttons and toggle controls in ways they weren\u0026rsquo;t designed for.\nThe app had become a Rube Goldberg machine. It worked, but adding anything new meant understanding all the existing moving parts and hoping nothing broke.\nThe Decision to Rebuild # We didn\u0026rsquo;t take this lightly. Rewriting a working application is risky — you\u0026rsquo;re spending money to get back to where you already are before you can move forward. But the writing was on the wall:\nThe Power App couldn\u0026rsquo;t scale further. Every new feature was getting harder and slower to build. Performance was hurting the business. Staff were avoiding the app when they could and falling back to spreadsheets. Maintenance risk was growing. The original developer\u0026rsquo;s availability was limited, and nobody else could work on it. The requirements were well understood. After two years of using the Power App, the business knew exactly what they needed. No discovery phase, no guesswork. That last point is underrated. The Power App had essentially served as a working prototype — an expensive one, but it meant we weren\u0026rsquo;t building from a blank page. We had screens, workflows, and edge cases already mapped out.\nHow We Built the Replacement # We chose Next.js with TypeScript, Ant Design for the UI, Azure AD for authentication, and Azure SQL Database for storage. The app runs on Azure App Service with Azure Functions handling background jobs like scheduled email notifications.\nRequirements and Mockups First # Before writing any code, we documented every feature the Power App had — and every feature they wished it had. We walked through the existing app screen by screen, cataloging functionality, business rules, and pain points. This gave us a clear requirements document and a set of UI mockups to build against.\nThis step matters more than people think. When you\u0026rsquo;re using AI to help generate code (more on that in a minute), the quality of what you get out is directly proportional to the quality of what you put in. Vague requirements produce vague code. Detailed requirements with mockups produce components that are 80-90% right on the first pass.\nAI-Assisted Development # Here\u0026rsquo;s where it gets interesting. This was a solo developer project with a tight timeline. Building a full-featured web application — authentication, role-based access, CRUD operations, PDF generation, email automation, responsive UI — would normally take months of dedicated work.\nOur AI tool of choice was Claude on the Max plan ($100/month). That\u0026rsquo;s it — one subscription, one developer, and a clear set of requirements. We used it extensively throughout the build. Not to replace the developer, but to accelerate the tedious parts:\nScaffolding and boilerplate. Setting up API routes, database queries, TypeScript types, form validation — the kind of code that\u0026rsquo;s necessary but not creative. With clear requirements, we could describe what an API endpoint needed to do and get a working implementation that just needed review and testing.\nUI components. Given a mockup and a component library (Ant Design), generating the initial component code was fast. \u0026ldquo;Build a form page with these fields, these validation rules, and this layout\u0026rdquo; produces something usable quickly. The developer\u0026rsquo;s job shifted from writing every line to reviewing, adjusting, and integrating.\nDatabase migrations. Translating a data model into SQL migration scripts, complete with indexes and constraints. Describing the schema in plain English and getting back working DDL.\nRepetitive patterns. Once we\u0026rsquo;d established how one CRUD feature worked (API route, service layer, component, types), generating the next five features followed the same pattern. The AI was good at applying established conventions consistently.\nSecurity hardening. We ran the codebase through security review — JWT validation, XSS protection, SQL injection prevention, input sanitization, security headers. The AI identified vulnerabilities and generated fixes, which we then verified.\nWhat AI didn\u0026rsquo;t do well: architectural decisions, complex business logic edge cases, performance optimization, and anything requiring deep understanding of how the pieces fit together. Those still required an experienced developer thinking carefully.\nThe net effect was that the core application was rebuilt in a few weeks. But here\u0026rsquo;s the part that doesn\u0026rsquo;t make the highlight reel: we spent significantly more time on validation than on building. The goal wasn\u0026rsquo;t just feature parity — it was parity or better on every workflow. That meant going through every screen, every edge case, every business rule from the Power App and verifying the new app handled it at least as well, if not better.\nQA Process # Building fast with AI makes a rigorous QA process non-negotiable. The build phase was the easy part — the validation phase was where the real time went. When code is generated rather than hand-typed, you can\u0026rsquo;t rely on the \u0026ldquo;I wrote it so I understand it\u0026rdquo; safety net. We implemented:\nCode review on every generated component. Nothing went in without the developer reading and understanding it line by line. Manual testing against the requirements doc. Every feature was tested against its original requirement, not just \u0026ldquo;does it compile.\u0026rdquo; Security review. We did a dedicated security pass, treating the app as if it were written by an untrusted junior developer. We found and fixed JWT validation gaps, missing input sanitization, console logging of sensitive data, and transaction handling issues. User acceptance testing. The actual users (the store staff) tested the app against their real workflows before go-live. Their feedback drove the final round of adjustments. Side-by-side comparison. We ran both the Power App and the web app in parallel for two weeks, ensuring the new app handled every scenario the old one did. What We Gained # The difference was night and day:\nSpeed. Pages load in under a second. Search is instant, even across tens of thousands of records. No delegation limits, no 500-row caps, no phantom missing results.\nPolish. This was the surprise win. With a proper component library and consistent UI patterns, the web app ended up far more polished than the Power App ever was. We could add modal dialogs for editing contacts, sales staff, and store details — all inline, without navigating away from the current screen. Form layouts were consistent across every page. Loading states, validation feedback, error messages — all uniform. In Power Apps, that level of UI consistency is a constant battle against the platform. In a web app with a design system like Ant Design, it\u0026rsquo;s the default.\nSecurity. Proper JWT-based authentication with Azure AD, role-based access control at the API level, parameterized queries, XSS protection, and security headers. The kind of security that\u0026rsquo;s table stakes for a web app but nearly impossible to retrofit onto a Power App.\nMaintainability. It\u0026rsquo;s a standard Next.js/TypeScript codebase in a git repository. Any developer who knows React can pick it up. There are pull requests, code reviews, diffs, and a clear project structure. The README explains how to run it. The CLAUDE.md documents the architecture for AI-assisted future development.\nExtensibility. Need a new feature? It\u0026rsquo;s a new API route and a new component, following established patterns. Need to change the PDF layout? It\u0026rsquo;s a pdfmake template in code, not a Power Automate flow calling a third-party connector. Need scheduled emails? It\u0026rsquo;s an Azure Function with an HTML template. Need a new modal for managing a related entity? Copy an existing one, adjust the fields, done.\nCI/CD. We set up deployment pipelines for both dev and production environments. Push to a branch, it deploys to dev. Merge to main, it goes to production. The Power App had\u0026hellip; \u0026ldquo;publish.\u0026rdquo;\nWhen Power Apps Still Makes Sense # This isn\u0026rsquo;t a \u0026ldquo;Power Apps is bad\u0026rdquo; article. Power Apps is the right tool when:\nYour data fits comfortably in SharePoint or Dataverse limits You have a small number of users (under ~20) The app is simple — a few screens, straightforward data entry, basic reporting You need something running this week, not this quarter The builder will also be the maintainer for the foreseeable future The camera parts store\u0026rsquo;s app had simply outgrown all of those conditions. It had thousands of records, complex business rules, multiple user roles, PDF generation, automated emails, and needed to be maintainable by someone other than the original builder.\nThe AI Development Takeaway # The real story here isn\u0026rsquo;t \u0026ldquo;Power Apps bad, custom code good.\u0026rdquo; It\u0026rsquo;s that AI-assisted development has changed the build-vs-buy calculation. Two years ago, rebuilding this app as a custom web application would have been hard to justify for a small business. The development cost would have been too high relative to the pain of living with the Power App\u0026rsquo;s limitations.\nBut with Claude Max at $100/month and a single experienced developer, we built a production-quality application in a few weeks. The real investment was in validation — weeks more of meticulous testing to ensure parity or better on every workflow. The developer\u0026rsquo;s role shifted from writing every line of code to architecting the solution, defining the requirements clearly, reviewing generated code, and handling the genuinely complex parts that require human judgment.\nThat\u0026rsquo;s the pattern I expect to see more of: businesses that started with low-code tools outgrowing them, and AI-assisted development making the jump to custom code practical on a small-business budget. The key ingredients are clear requirements (ideally from having already run the low-code version), an experienced developer who can architect and review, and a disciplined QA process that doesn\u0026rsquo;t trust generated code blindly. The build is the fun part. The validation is where you earn the trust.\nThe camera parts store is now running on an app that\u0026rsquo;s faster, more secure, easier to extend, and maintainable by any competent developer. Total AI tooling cost: a few months of a $100 subscription. That\u0026rsquo;s not magic — it\u0026rsquo;s the practical reality of knowing what to build and having better tools to build it with.\n","date":"Mar 6, 2026","image":"/images/power-app-to-web-app-migration/power-app-to-web-app-migration-hero_hu_b958a9a6bf05b114.webp","permalink":"/2026/03/05/power-app-to-web-app-migration/","section":"Post","summary":"A boutique camera parts dealer outgrew their Power App. Here’s why we moved to a custom web app, the problems that forced the switch, and how AI-assisted …","title":"Why We Rebuilt a Power App as a Web App (And How AI Helped Us Ship It Fast)","type":"posts"},{"content":" CommunityDays.org turned three on October 13th. I still can\u0026rsquo;t quite believe that.\nWhat started as a scramble to fill a gap when SPSEvents.org shut down has turned into something I\u0026rsquo;m genuinely proud of. It\u0026rsquo;s not perfect, and there\u0026rsquo;s always more to do, but three years in feels like a good time to look back at how we got here.\nHow It Started # When SPSEvents.org announced it was going away, it left a real hole. That site had been the central hub for SharePoint Saturdays and Microsoft community events for years. Organizers relied on it. Speakers browsed it to find events to submit to. Attendees used it to figure out what was happening near them.\nI figured someone needed to step up, and I had the skills to try. So I did.\nIt was a lot of late nights and weekends. SoHo Dragon sponsored the effort, Microsoft backed it, and Jeff Teper was kind enough to announce the launch at Ignite — which was a surreal moment for me. But the idea itself was always simple: give organizers a place to list their events and give everyone else a place to find them.\nBy the Numbers # Hundreds of organizers. Thousands of sessions. Speakers from all over the world covering Microsoft 365 (M365), Power Platform, Azure, and more.\nI\u0026rsquo;m proud of those numbers. But honestly, the numbers aren\u0026rsquo;t the part that keeps me going.\nThe Stories That Stick With Me # After the M365 DC event, Charles Lakes II came up to me and said the site had helped him find more events, get in front of more audiences, and that it played a part in him earning his MVP. I want to be clear — Charles earned that through his own hard work. But hearing that Community Days helped him find those opportunities? That meant a lot to me.\nAt that same event, I sat in on a Power Platform session from Diego Da Silva who was just getting started as a speaker. You could see it click for him — that moment when someone realizes they have something worth sharing and people want to hear it. That\u0026rsquo;s what community events do. They give people a stage before they even know they\u0026rsquo;re ready for one.\nThose are the moments I think about when the work gets heavy.\nThe People Who Make It Work # Community Days doesn\u0026rsquo;t run on one person. There\u0026rsquo;s a group of people who show up consistently — reviewing events, promoting the platform, flagging bugs, keeping things funded and running. I could list every name but the list would get long and I\u0026rsquo;d inevitably leave someone out. You know who you are, and I appreciate what you do. You can see the full list of contributors and sponsors on the Community Days about page.\nOn the partnerships side, Adis Jugo and the team at run.events and Domagoj Pavlesic and the crew at Sessionize have been real partners in this — not transactional, but built on a shared belief that community events deserve good tools and good visibility. Working with them has made Community Days better.\nWhy I Keep Doing This # People ask me sometimes. It\u0026rsquo;s volunteer work. It takes real time away from other things. There\u0026rsquo;s no big team behind the curtain.\nThe honest answer is that I enjoy it. I\u0026rsquo;ve been part of the Microsoft community for a long time, and I\u0026rsquo;ve seen what happens when people share what they know — when they mentor someone new, when they give up a Saturday to teach a room full of strangers something useful. I want to support that however I can.\nCommunity Days is free for organizers. The event listings, the call-for-sponsorship tools, the Speakerboard — all free. I feel strongly that the moment you start putting barriers in front of community, it stops being community.\nLooking Ahead # I\u0026rsquo;ll be honest — the last three years haven\u0026rsquo;t always been easy. There are stretches where the to-do list feels infinite and the time feels short. But then someone shares a story about how the site helped them, or just says thanks, and it puts things back in perspective.\n394 events listed. On to year four. Thank you to everyone who\u0026rsquo;s been part of this — the organizers, the speakers, the attendees, and the people working alongside me to keep it going. I appreciate all of you.\nGet Involved # If any of this resonates with you, there are a few ways to get involved:\nList your event on CommunityDays.org — it\u0026rsquo;s free for organizers Browse the Speakerboard at CommunityDays.org/speakerboard — speakers who present at community events are automatically listed, making them discoverable by other event organizers Organize or help with community events — the Microsoft Global Community Initiative (MGCI) runs regular office hours and training for new and existing event organizers Sponsor a community event — browse events looking for sponsors through the call-for-sponsorship listings Want to help out? — reach out through the contact page and let us know how you\u0026rsquo;d like to contribute Just show up — attend an event near you. That\u0026rsquo;s how most of us got started Related Links # Celebrating Three Years of CommunityDays.org — Microsoft Tech Community announcement CommunityDays.org — Browse upcoming community events About Community Days — Meet the team and sponsors behind the platform Jeff Teper Announces CommunityDays.org at Ignite — Karuana Gatimu interviews Jeff Teper about the launch Celebrating Three Years of Community Days: A Love Letter — Diego Da Silva\u0026rsquo;s perspective on the Community Days journey ","date":"Oct 13, 2025","image":"/images/community-days-three-years/community-days-three-years-hero_hu_51ce5e7b92bb8b5b.webp","permalink":"/2025/10/12/community-days-three-years/","section":"Post","summary":"CommunityDays.org turns three. A look back at how it started, what we’ve built, and the people who made it happen.","title":"Three Years of Community Days: Built by the Community, For the Community","type":"posts"},{"content":"Contributing to the tech community takes time — something that gets harder to find as life gets busier. That\u0026rsquo;s part of why this recognition means so much.\nI\u0026rsquo;m happy to share that I\u0026rsquo;ve been renewed as a Microsoft MVP for 2025-2026.\nThis is my ninth year in the program, and it still means as much now as it did when I first received the award back in 2017. It\u0026rsquo;s not the kind of thing you get used to — every renewal is a reminder that the work you\u0026rsquo;re putting into the community is being recognized.\nWhat the Microsoft MVP Program Provides # The MVP award is an invaluable resource to me personally. It gives me a more direct line of communication with the Microsoft product teams and employees — the ability to provide feedback, collaborate on things together, and get early insight into where the platform is heading. That access makes me better at what I do, and it makes the guidance I share with the community more informed.\nThe award also validates the work I\u0026rsquo;m doing in the Microsoft space and the impact on its community. The majority of my contributions live on the CommunityDays.org platform — organizing events, supporting other organizers, and helping grow the community events ecosystem.\nBut that\u0026rsquo;s just one piece. I\u0026rsquo;m still highly technical and sharing knowledge through blog posts and talks. I\u0026rsquo;m mentoring the next generation of developers coming into this space and solving real customer problems every day. I\u0026rsquo;m running events locally and visiting and supporting other events around the world.\nAnd it helps with community events in a real, tangible way. When we organize and run events for our local community, people are more confident that the event will be of value when it\u0026rsquo;s organized and delivered by Microsoft MVPs. That credibility opens doors — for speakers, for sponsors, and most importantly for attendees who take time out of their day to show up and learn.\nWhat This Past Year Looked Like # On the MGCI (Microsoft Global Community Initiative) side, I joined the board and participate in weekly meetings with Microsoft to help steer the direction of Community Days. A few times a month there are MGCI board meetings, and I\u0026rsquo;m running the Tech and Tools subcommittee — focused on the platform and tooling that powers CommunityDays.org. I also pop in on the MGCI Community Event Organizer Training \u0026amp; Office Hours to help new organizers get started.\nA new role this year: a recurring bi-weekly appearance on the M365 General Dev Special Interest Group (SIG). It\u0026rsquo;s been a great way to stay connected with the broader developer community and share what we\u0026rsquo;re building.\nBehind the scenes, I\u0026rsquo;ve put thousands of hours into developing on CommunityDays.org — the platform that supports community events worldwide. That work doesn\u0026rsquo;t always get visibility, but it\u0026rsquo;s foundational to everything else.\nEvents — Running, Attending, Supporting # On the events front, this was a big year. I helped bring M365 NJ back, ran M365 NYC for our biggest and best event since the pandemic, organized AI Conference (AICO) New York City and M365 Philly, and helped out with M365 DC. I also attended events like TechCon Atlanta and M365 Memphis! — showing up to support other communities matters just as much as running your own.\nMentorship and Connections # Beyond events, I\u0026rsquo;ve been focused on mentoring — helping people navigate their careers in this space, making introductions, and connecting folks with job opportunities when I can. Some of the most rewarding work isn\u0026rsquo;t visible at all. It\u0026rsquo;s a conversation that helps someone land their next role or find the confidence to submit their first session.\nThe Ideas That Didn\u0026rsquo;t Ship # I\u0026rsquo;ll also be honest about the handful of initiatives that started with momentum but didn\u0026rsquo;t have enough bandwidth to fully develop. Time and resources are finite. Some ideas need to wait for the right season — and knowing when to put something on hold is just as important as knowing when to start.\nContinuing to Contribute While Life Changes # I\u0026rsquo;ll be honest — the landscape of contributing has changed for me personally. Early in my MVP journey, time was abundant. I could travel to events, write late into the night, and pour hours into side projects without thinking twice. Now I\u0026rsquo;m a father of two young girls, and that math is different. I\u0026rsquo;m doing what I can to give back while also being an involved parent.\nThat tension has actually made me more intentional about where I spend my time. I focus on contributions that have the most impact — the events that reach the most people, the blog posts that solve real problems, the mentoring conversations that help someone take their next step. Less time means I have to make it count.\nThe goal hasn\u0026rsquo;t changed since before I ever had the MVP title: if something I share helps one person solve a problem or learn something new, that\u0026rsquo;s the mission accomplished. I just have to be smarter about how I get there now.\nThank you to Microsoft and to everyone in the community who makes this worth doing.\nIf you\u0026rsquo;re thinking about contributing to the community — speaking, organizing events, or sharing what you\u0026rsquo;ve learned — start small and get involved. The ecosystem grows stronger every time someone decides to give back.\n","date":"Aug 1, 2025","image":"/images/renewed-microsoft-mvp-2025/renewed-microsoft-mvp-2025-hero_hu_c52f1de9d2d74667.webp","permalink":"/2025/07/31/renewed-microsoft-mvp-2025-2026/","section":"Post","summary":"Renewed as a Microsoft MVP for 2025-2026 — what the program means to me and why it matters for our community.","title":"Renewed as Microsoft MVP for 2025-2026","type":"posts"},{"content":" If you\u0026rsquo;ve looked at your SharePoint storage report lately and wondered where all the space went, check version history. Depending on your tenant\u0026rsquo;s configuration — up to 500 major versions, or Microsoft\u0026rsquo;s newer \u0026ldquo;automatic\u0026rdquo; model with no fixed cap — there\u0026rsquo;s no expiration by default. Every save, every autosave from co-authoring, every edit creates a version that sticks around forever.\nWe found libraries where version history was consuming 3-4x the space of the current files. Co-authoring makes it worse — a one-hour session with multiple editors can generate dozens of versions. Multiply that across hundreds of sites and it adds up fast.\nMicrosoft added tenant-level version controls in the SharePoint Admin Center (Settings \u0026gt; Version history limits), which works for broad policy. But we needed per-library control baked into our site provisioning pipeline — applied consistently whether a site was created today or two years ago, without relying on someone remembering to toggle an admin setting.\nWe needed a consistent policy: 100 major versions, minor versions enabled, and automatic expiration after 60 days. Applied to every site in the tenant — both existing sites and every new site as it\u0026rsquo;s provisioned. Here\u0026rsquo;s how we did it.\nThe Versioning Policy # We reviewed storage reports and asked the business how far back they\u0026rsquo;d ever actually needed to roll back. The answer was almost always days, not months. We landed on:\nSetting Value Rationale Major versions 100 Covers months of active editing, even on busy documents Minor versions Enabled Supports draft/publish workflows Version expiration 60 days Auto-trims anything older than two months The 60-day expiration is the key lever. It bounds version history regardless of edit frequency. Both major and minor versions are subject to the policy — anything older than 60 days is eligible for cleanup.\nApplying Versioning at Provisioning Time # Our sites are provisioned via an Azure Function queue trigger that creates the site, applies a Patterns and Practices (PnP) template, copies template folders, and configures versioning. Here\u0026rsquo;s the relevant section:\n# Enable versioning on the Documents library Write-Host \u0026#34;Enabling versioning on \u0026#39;Documents\u0026#39; library.\u0026#34; Set-PnPList -Identity \u0026#34;Documents\u0026#34; ` -EnableVersioning $true ` -MajorVersions 100 ` -EnableMinorVersions $true ` -Connection $newSiteConnection Write-Host \u0026#34;Setting expiration for versions in \u0026#39;Documents\u0026#39; library to 60 days.\u0026#34; Set-PnPList -Identity \u0026#34;Documents\u0026#34; ` -ExpireVersionsAfterDays 60 ` -Connection $newSiteConnection Note the two separate Set-PnPList calls. Combining -ExpireVersionsAfterDays with the other versioning parameters in a single call can produce inconsistent results depending on your PnP PowerShell version. We learned that the hard way — split them and it\u0026rsquo;s reliable.\nRemediating Existing Sites # New sites are handled, but existing sites still had default settings and months of accumulated version history. We built a bulk remediation script that reads a site inventory CSV and applies the same policy:\nImport-Module PnP.PowerShell # Path to the CSV file containing site inventory $csvFilePath = \u0026#34;sites.csv\u0026#34; # Read the CSV file $sites = Import-Csv -Path $csvFilePath # Iterate through each active site foreach ($site in $sites) { if ($site.Status -eq \u0026#39;C\u0026#39; -and -not [string]::IsNullOrWhiteSpace($site.SiteUrl)) { try { $siteUrl = $site.SiteUrl Connect-PnPOnline -Url $siteUrl -ClientId \u0026#34;your-app-registration-client-id\u0026#34; # Apply versioning policy Set-PnPList -Identity \u0026#34;Documents\u0026#34; ` -EnableVersioning $true ` -MajorVersions 100 ` -EnableMinorVersions $true Set-PnPList -Identity \u0026#34;Documents\u0026#34; ` -ExpireVersionsAfterDays 60 Write-Host \u0026#34;Successfully updated site: $siteUrl\u0026#34; -ForegroundColor Green } catch { Write-Host \u0026#34;Failed to update site: $siteUrl. Error: $_\u0026#34; -ForegroundColor Red } finally { Disconnect-PnPOnline } } } The CSV tracks every site with a status column — C for active, R for retired. The script skips retired sites and empty URLs, so it\u0026rsquo;s safe to run against the full inventory.\nWhat Happens When Expiration Kicks In # -ExpireVersionsAfterDays 60 doesn\u0026rsquo;t delete old versions immediately — SharePoint handles it as a background job. A few things to know:\nBackground timer job. Versions older than 60 days become eligible for deletion, but actual cleanup can take days on large libraries. Current version is never expired. Only historical versions are affected. Recycle bin delay. Deleted versions go to the recycle bin (93 days total across first and second stage). True storage recovery happens after the recycle bin clears. If you need to reclaim storage immediately:\nClear-PnPRecycleBinItem -All -Force -Connection $siteConnection Not reversible. We ran this selectively on sites with the highest storage pressure, not tenant-wide.\nMonitoring the Results # After rolling this out, we tracked storage in SharePoint Admin Center. Within a few weeks, total tenant storage dropped as background expiration worked through the backlog. The bigger win was that storage growth flattened — no more unbounded accumulation.\nLessons Learned # Split the Set-PnPList calls. Versioning and expiration in separate calls avoids parameter combination edge cases.\nRun remediation quarterly. Settings can drift — someone changes a library manually, or a new library gets created outside the pipeline. Re-running the bulk script catches it.\nWatch the recycle bin. Expired versions go to the recycle bin, not into thin air. If you\u0026rsquo;re trying to reclaim storage urgently, clear the recycle bins too.\nCommunicate before you enforce. We notified users that versions older than 60 days would be automatically cleaned up. Nobody complained — most didn\u0026rsquo;t know versioning existed. But it\u0026rsquo;s good governance to communicate before changing retention behavior.\nWrapping Up # SharePoint versioning is one of those features that works against you when left on autopilot. The defaults are safe but wasteful. By enforcing a consistent policy — 100 major versions with 60-day expiration — across every site in the tenant, we reduced storage consumption significantly and eliminated a growing cost problem.\nThe approach is two-pronged: apply the policy automatically when new sites are provisioned, and run a bulk remediation script to bring existing sites in line. Both use PnP PowerShell\u0026rsquo;s Set-PnPList, and both can run unattended.\nIf your tenant storage report has been creeping upward and you\u0026rsquo;re not sure why, check your version history. Chances are that\u0026rsquo;s where the space is going. And if you\u0026rsquo;re managing versioning across a tenant differently — or have hit edge cases with ExpireVersionsAfterDays — I\u0026rsquo;d like to hear about it in the comments.\n","date":"Jun 15, 2025","image":"/images/managing-document-library-versioning-tenant/managing-document-library-versioning-tenant-hero_hu_f1fb65ffdbbb4a70.webp","permalink":"/2025/06/14/managing-document-library-versioning-tenant/","section":"Post","summary":"Version history in SharePoint can quietly eat your storage. Here’s how we enforce consistent versioning and auto-expiration across every site in a tenant using …","title":"Managing Document Library Versioning and Expiration Across a SharePoint Tenant","type":"posts"},{"content":"Processing PDF invoices by hand doesn\u0026rsquo;t scale. I was working on a reconciliation project where the finance team had thousands of PDF invoices that needed to be matched against records in their accounts payable system. The data lived in the PDFs — invoice numbers, dates, totals, vendor names — but getting it out meant someone had to open each one, find the fields, and type them into a spreadsheet. It was slow, error-prone, and took way too much time every month.\nI wanted to automate the extraction end to end. Drop a PDF in, get structured data out. No manual intervention, no per-format configuration. The system needed to handle invoices, statements, contracts — whatever showed up in the inbox.\nThe twist was that these invoices came from hundreds of different vendors, and every single one formats their invoices differently. Different layouts, different labels, different date formats, different ways of presenting totals. Writing extraction rules per vendor wasn\u0026rsquo;t going to work — there were too many, and new ones showed up all the time.\nWhat I ended up building was a two-stage AI pipeline using AWS Textract for OCR and Amazon Bedrock for the AI normalization layer. Textract reads the page. Bedrock figures out what it all means and boils every format down to one consistent JSON structure — regardless of how the vendor laid out the invoice.\nThe Real Problem: Every Vendor\u0026rsquo;s Invoice Looks Different # This was the fundamental challenge. We weren\u0026rsquo;t dealing with one invoice format — we were dealing with hundreds. Every vendor sends their invoices laid out differently. Some have the invoice number at the top right. Others bury it in a table. Some label it \u0026ldquo;Invoice #\u0026rdquo;, others call it \u0026ldquo;Reference No.\u0026rdquo; or \u0026ldquo;Document ID.\u0026rdquo; Dates show up as \u0026ldquo;01/15/2025,\u0026rdquo; \u0026ldquo;January 15, 2025,\u0026rdquo; \u0026ldquo;2025-01-15,\u0026rdquo; or \u0026ldquo;15 Jan 25.\u0026rdquo; Totals might be in a summary box, a footer line, or the last row of a table.\nIf you try to solve this with traditional OCR alone, you end up writing extraction rules for each vendor\u0026rsquo;s format. That might work for your top 10 vendors, but when you have hundreds — and new ones showing up regularly — it\u0026rsquo;s a losing game. You\u0026rsquo;d spend more time maintaining the rules than you\u0026rsquo;d save on manual entry.\nThat\u0026rsquo;s where the AI comes in. The AI is the normalizer. It doesn\u0026rsquo;t care that Vendor A puts the invoice number in the header and Vendor B puts it in line 3 of a table. You give it the raw extracted text and tell it: \u0026ldquo;Find me the invoice number, the date, the total, and the vendor.\u0026rdquo; It figures out which field is which regardless of layout, labeling, or format — and returns it in a single consistent JSON structure every time.\nWhy Two AI Services Instead of One? # So if the AI handles the normalization, why not just send the PDF directly to the model and skip OCR entirely?\nI actually tried this first. Bedrock supports direct PDF input — you base64-encode the file and send it straight to the model. But there are practical limits. Bedrock caps direct PDF input at 5MB, and a lot of real-world invoices — especially scanned multi-page documents — blow right past that.\nMore importantly, even when the files were small enough, the results were inconsistent. The model would miss table data, misread amounts, or confuse fields that were visually close together on the page. Out of roughly 15,000 documents, the direct-to-Bedrock approach failed on close to 100% of them when used as the primary path. It was trying to solve two problems at once, and neither one got the attention it needed.\nThe two problems are:\nThe spatial problem — Where is the text on the page? What\u0026rsquo;s a form label vs. a value? What belongs to which table column? This is a vision problem. The semantic problem — Out of all these fields, which one is the invoice number? Is this date the invoice date or the due date? Is this amount the subtotal or the total? This is a comprehension problem. Textract is purpose-built for the spatial problem. It doesn\u0026rsquo;t just do OCR — it identifies key-value pairs (like \u0026ldquo;Invoice Date: 12/15/2025\u0026rdquo;), detects table structures with rows and columns, and maps out form fields. It understands the layout of the page in a way that a general-purpose language model can\u0026rsquo;t reliably match.\nBedrock handles the semantic problem — the normalization. I take all that structured data from Textract and hand it to the AI model. Now the model doesn\u0026rsquo;t have to figure out where things are on the page. It just has to look at a list of key-value pairs and lines of text and decide: \u0026ldquo;This is the invoice number. This is the date. This is the total.\u0026rdquo; And it does that consistently, regardless of whether the invoice came from a massive distributor or a one-person shop with a Word document template.\nThe combination means Textract does what it\u0026rsquo;s best at (reading the page), and the AI does what it\u0026rsquo;s best at (understanding the content and normalizing it into a single format). Neither one alone would have worked nearly as well.\nThe AWS Architecture # The whole pipeline is serverless. Drop a PDF into an S3 bucket, and everything else happens automatically — no servers to manage, no workers to monitor.\nHere\u0026rsquo;s the flow:\nPDF lands in S3 — uploaded to the inbox/ prefix of an S3 bucket S3 Event Notification fires — triggers automatically when a new object appears in inbox/ SQS Queue receives the event — buffers the work and handles retries if the Lambda fails Lambda picks up the message — runs Textract, sends results to Bedrock, writes output to S3 and the database PDF moves through prefixes — inbox/ → processing/ → processed/ (or error/ if something fails) The S3 prefix movement is a nice pattern. You always know the state of a document by where it sits in the bucket. If something fails mid-processing, you can see exactly which files are stuck in processing/ and investigate.\nThe Lambda: Textract + Bedrock in One Function # The Lambda function does all the heavy lifting. When an SQS message arrives, it parses the S3 event, grabs the PDF, and runs it through both AI stages.\nStep 1: Run Textract with FORMS + TABLES\nconst result = await textract.send( new StartDocumentAnalysisCommand({ DocumentLocation: { S3Object: { Bucket: bucket, Name: key } }, FeatureTypes: [\u0026#34;FORMS\u0026#34;, \u0026#34;TABLES\u0026#34;] }) ); The FORMS and TABLES feature types are key. Basic OCR just gives you text. With these enabled, Textract identifies key-value pairs (like \u0026ldquo;Invoice Date: 12/15/2025\u0026rdquo;) and detects table structures with rows and columns. This structured data is what makes the Bedrock step so much more accurate.\nStep 2: Send to Bedrock for AI Extraction\nHere\u0026rsquo;s where the normalization happens. I send the structured Textract data to Amazon Bedrock with a prompt that tells the model exactly what I need back.\nThe prompt is specific about the output format:\nYou extract key information from invoices or invoice-like documents. Focus on identifying the invoice number - it is the most important field. Return ONLY valid JSON matching exactly: { \u0026#34;document_type\u0026#34;: \u0026#34;invoice|statement|contract|other\u0026#34;, \u0026#34;specific_number\u0026#34;: { \u0026#34;label\u0026#34;: \u0026#34;Invoice Number|Claim Number|PO Number|...\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;string or null\u0026#34;, \u0026#34;confidence\u0026#34;: 0.0 }, \u0026#34;key_fields\u0026#34;: { \u0026#34;date\u0026#34;: null, \u0026#34;due_date\u0026#34;: null, \u0026#34;total\u0026#34;: null, \u0026#34;tax\u0026#34;: null, \u0026#34;vendor\u0026#34;: null, \u0026#34;customer\u0026#34;: null }, \u0026#34;summary\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;notes\u0026#34;: [] } Rules: - Confidence is 0.0 to 1.0. If not found, value=null and confidence=0. - ALL dates MUST use format MM/DD/YYYY. - ALL dollar amounts MUST use format $X,XXX.XX. Getting the prompt right is critical. This is the part that took the most iteration. The AI will do exactly what you tell it to — and if you\u0026rsquo;re not specific enough, you\u0026rsquo;ll get inconsistent results across thousands of documents.\nA few things I learned through trial and error:\nDefine the exact JSON schema. I tell the model exactly what shape to return. No extra keys, no variations. Without this, some documents come back with extra fields, others are missing fields, and your parsing code has to handle every variation. Lock down the schema and the output is predictable. Enforce formats in the prompt. Dates as MM/DD/YYYY, amounts as $X,XXX.XX. This is huge. Without explicit format rules, the AI will return dates in whatever format it finds on the document — \u0026ldquo;January 15, 2025\u0026rdquo; from one vendor, \u0026ldquo;2025-01-15\u0026rdquo; from another, \u0026ldquo;01/15/25\u0026rdquo; from a third. The whole point of this pipeline is normalization, so the prompt has to enforce it. If you don\u0026rsquo;t do this here, you end up writing format conversion code downstream for every variation. Confidence scores. The model rates its own confidence on the invoice number. A 0.95 means it\u0026rsquo;s pretty sure. A 0.4 means the document might not even have an invoice number. This is valuable downstream for deciding what needs human review vs. what can flow through automatically. Give the model a place to flag problems. The notes array lets the AI say \u0026ldquo;multiple invoice numbers found\u0026rdquo; or \u0026ldquo;document appears to be a statement not an invoice.\u0026rdquo; Without this, the model silently picks one and you never know it was uncertain. Much better to have it tell you. Step 3: Write the Results\nThe Lambda writes a .ai.json file to the processed/ prefix in S3 and upserts a database record using a SQL MERGE on source_file — so reprocessing the same PDF updates instead of duplicating. After writing, the PDF moves from processing/ to processed/, or to error/ if something fails.\nThe Textract Fallback — When OCR Fails # This one surprised me. Some PDFs just don\u0026rsquo;t work well with Textract. Scanned documents with poor quality, unusual layouts, or PDFs that are actually just embedded images. Textract would time out or return very few blocks.\nRather than failing the entire document, I built a fallback: send the PDF directly to Bedrock. Bedrock can accept PDFs directly (up to ~5MB). The extraction quality isn\u0026rsquo;t as good as the two-stage approach — the model has to handle both the spatial and semantic problems — but it\u0026rsquo;s significantly better than returning nothing.\nThe .ai.json output records whether Textract was used or if it fell back to direct PDF (\u0026quot;status\u0026quot;: \u0026quot;FALLBACK_PDF\u0026quot;), so you can audit which documents might need a closer look.\nThis is a non-ideal workaround. I\u0026rsquo;m pointing this out for transparency. The direct PDF path loses the structured key-value pairs and table data that Textract provides. But in practice, getting 80% of the fields from a difficult document is better than getting 0%.\nWhen Everything Fails — The Error Pipeline Matters # Here\u0026rsquo;s something that doesn\u0026rsquo;t get talked about enough in AI pipeline posts: you need a plan for errors, not just results.\nOut of ~15,000 documents, the vast majority processed fine through the two-stage pipeline. But a meaningful number didn\u0026rsquo;t — Textract timed out, Bedrock returned malformed JSON, the PDF was corrupt, the file was too large, or the document just wasn\u0026rsquo;t an invoice at all (contracts, cover letters, blank pages).\nWhen both Textract and the direct PDF fallback fail, the document lands in the error/ prefix in S3 with a detailed .error.json file next to it. That file captures what went wrong — which stage failed, the error message, timestamps. This gives you a clear picture of what needs attention.\nBut here\u0026rsquo;s the reality: some documents still end up requiring manual entry. No pipeline handles 100% of real-world data. The goal isn\u0026rsquo;t zero errors — it\u0026rsquo;s making the error path visible and manageable. You want to know exactly which documents failed, why they failed, and have a clean way to either fix and reprocess them or route them to a person.\nA few things that helped:\nError sidecar files per stage. If Textract fails, a .textract-error.json gets written. If Bedrock fails, a .bedrock-error.json gets written. You can tell at a glance which stage broke. The error/ prefix preserves the original folder structure. If a file was in inbox/vendor-123/invoice.pdf, it lands in error/vendor-123/invoice.pdf. Easy to find, easy to reprocess — just move it back to inbox/. Error counting scripts. I built simple scripts to scan the error files and produce counts by error type. This tells you if you have a systemic issue (Textract throttling, Bedrock model changes) vs. one-off bad files. The pipeline that processes 14,800 out of 15,000 documents automatically is valuable. But the system that tells you exactly which 200 need human attention — and why — is what makes it production-ready.\nWhat I\u0026rsquo;d Do Differently # A few things I\u0026rsquo;ve been thinking about for the next iteration:\nStore dates and amounts as proper types. Right now dates are nvarchar and amounts are varchar with dollar signs. This means every comparison requires TRY_CAST and REPLACE gymnastics. It works, but it\u0026rsquo;s fragile. I\u0026rsquo;d normalize these at write time in the Lambda instead. Add a review queue for low-confidence extractions. The confidence scores are there but nothing surfaces the low-confidence results for human review yet. A second SQS queue that catches anything under 0.7 confidence and routes it to a review UI would be a natural next step. Dead letter queue for persistent failures. Right now failed documents land in the error/ prefix in S3, which works but requires manual inspection. A DLQ with CloudWatch alarms would make this more operationally solid. Conclusion # The core problem was never \u0026ldquo;how do I OCR a PDF.\u0026rdquo; It was \u0026ldquo;how do I take invoices from hundreds of vendors — all with different layouts, labels, and formats — and normalize them into one consistent structure.\u0026rdquo; That\u0026rsquo;s the problem the two-stage pipeline solves.\nTextract handles the reading. The AI handles the normalization. You don\u0026rsquo;t write rules per vendor. You don\u0026rsquo;t maintain a mapping table of \u0026ldquo;Vendor A calls it Reference No., Vendor B calls it Invoice #.\u0026rdquo; The model figures that out, and every document comes out the other side in the same JSON format ready for matching.\nIf you\u0026rsquo;re evaluating AWS services for document processing, I\u0026rsquo;d recommend starting with Textract + Bedrock together rather than either one alone. Textract alone gives you raw data in whatever format the vendor decided to use. An LLM alone struggles with the spatial layout of real documents. The combination is where it clicks.\nI\u0026rsquo;m still iterating on the matching and reconciliation side of this — automatically pairing the extracted data with accounts payable import records using vendor number, date, and amount as a composite key. If there\u0026rsquo;s interest, I\u0026rsquo;ll cover that in a follow-up post.\nHave you dealt with multi-format invoice extraction? I\u0026rsquo;d be curious whether you went the rules-based route or let AI handle the normalization. Drop a comment below — I\u0026rsquo;d love to hear what worked and what didn\u0026rsquo;t.\n","date":"Mar 5, 2025","image":"/images/textract-plus-bedrock-two-stage-ai-pipeline/textract-plus-bedrock-two-stage-ai-pipeline-hero_hu_1d953da52d0b6941.webp","permalink":"/2025/03/04/textract-plus-bedrock-two-stage-ai-pipeline/","section":"Post","summary":"How I built a serverless pipeline using AWS Textract for OCR and Amazon Bedrock for AI normalization to extract structured data from invoices across hundreds of …","title":"Building a Two-Stage AI Pipeline for Invoice Processing with AWS Textract and Amazon Bedrock","type":"posts"},{"content":"In my previous article, Build a Site Directory with PnP Search Web Parts, I walked through how to create a dynamic site directory using PnP Modern Search. While that setup provides a powerful and flexible way to display SharePoint sites, you may have noticed a limitation—SharePoint doesn’t allow sorting by the Site Title property out of the box.\nThis article covers the extra step needed to sort your site directory alphabetically by title. The trick? Leveraging RefinableString fields to make the Site Title sortable. It’s a simple process with just a few tweaks, and by the end of this guide, you’ll have a properly sorted site directory in no time. Let’s dive in.\nUpdating the Search Schema # Navigate to the SharePoint Admin Center\nExpand ‘Advanced’ then click ‘More Features’ and finally click ‘Open’ under the ‘Search’ group\nNext click ‘Manage Search Schema’\nNext enter ‘RefinableString’ in the search box and click the green button\nHover over any of the available properties and find the drop down, then click ‘Edit Map Property’\nIt’s not critical on what number refinable string you choose\nScroll all the way to the bottom and click ‘Add a Mapping’\nEnter ‘display’ and click ‘Find’, then select Basic:displaytitle and click O\nVerify the mapped managed property and click ‘OK’\nThis completed the Search Schema changes. These can take quite some time to take effect so be patient. It could be a day before you see it working.\nNext navigate to the SharePoint page with the PnP Search Web Part configured as a Site Directory\nNOTE: The following steps are only necessary if you have customized the Custom template.\nEdit the page and open the web part properties of the Search Results web part On the second page, click on the curly braces { } Take a copy of the entire Change the template to Debug. This is so that you can visually see the property to ensure that the search property has taken effect.\nGo back to the first page of the web part properties, enter the name of the RefinableString## you modified and hit Enter.\nThe result should appear on the left hand site – most likely it will read null to begin with. It will eventually populate. It can appear at any time but at least wait 8 hours or a full day. Recheck the steps in the search config but at this point there is not much more to do but wait.\nWhat to do if the site title will no show up?\nIf it’s just not showing for a few sites\nUpdate the Site Title of the site. Change it temporarily and then change it back Trigger a reindex on the 1 site If it’s not showing up for many sites\nReindex all the sites – https://pnp.github.io/script-samples/spo-reindex-sites/README.html?tabs=pnpps [remove the filter in the script to have it index all sites] Recrawling puts strain on the service as a whole so it’s not meant to be run over and over as it can take weeks to finish on very large sites.\nAfter the crawl property appears move on to the next step.\nApply the Sort # Navigate back to the Search web part page and edit the web part properties\nClick ‘Edit sort settings’\nType in your RefinableString## into the Field name box, click Default Sort and then click ‘Add and save’\nThe sorting is now set to alphabetical order from A-Z by default.\nNext go to the second page of the web part properties\nSet the template back to what it previously was. In our case Custom and then click on the { } to edit the custom template\nCopy / Paste in the template, Save and Republish.\nThe final result should be in order by site title.\nWrap Up # Sorting your SharePoint site directory by title might not be possible out of the box, but with a little creativity—leveraging RefinableString fields—it becomes a straightforward solution. By following these steps, you can ensure your directory is organized in a way that makes finding sites easier for users.\nThis small but impactful tweak enhances usability and keeps your directory structured exactly how you need it. If you’re already using PnP Search Web Parts, this is a great optimization to implement. Have questions or run into issues? Drop a comment—I’d love to hear how this worked for you!\n","date":"Feb 2, 2025","image":"/images/sorting-your-sharepoint-site-directory-by-title-with-pnp-search/sorting-your-sharepoint-site-directory-by-title-with-pnp-sea-cc131eac_hu_4ac3d4f234b7de35.webp","permalink":"/2025/02/02/sorting-your-sharepoint-site-directory-by-title-with-pnp-search/","section":"Post","summary":"In my previous article, Build a Site Directory with PnP Search Web Parts, I walked through how to create a dynamic site directory using PnP…","title":"Sort Your SharePoint Site Directory Alphabetically","type":"posts"},{"content":"This article will demonstrate how PnP Search Web Parts can be used to build a comprehensive Site Directory that not only enhances navigation but also improves the overall user experience by providing a centralized resource for accessing various sites within your organization.\nPre-Requisites # PnP Modern Search – Search Web Parts – v4\nFrom your SharePoint site\nInstructions # Create a new Page\nEdit the Page\nAdd the PnP Search Results Web Part\nNext click ‘Configure’\nNext click ‘SharePoint Search’\nNext click ‘Customize’ under ‘Layouts slots’\nAdd SiteUrl — mapped to SPSiteUrl, click ‘Save’\nEdit the Query Template, using this as my base. It will show all ‘Sites’ and not the app catalog or my sites.\ncontentclass:STS_Site -“{TenantUrl}/sites/contenttypehub” -“{TenantUrl}/sites/appcatalog” -SiteTemplate:SPSPERS\ncontentclass:STS_Site -\u0026#34;{TenantUrl}/sites/contenttypehub\u0026#34; -\u0026#34;{TenantUrl}/sites/appcatalog\u0026#34; -SiteTemplate:SPSPERS ** Thanks Kasper Larsen for the comment and suggestion to use the tokens for more resuability! **\nMake sure to hit Apply to save the query.\nScroll down to the Paging Options\nFor our example we want to show as many sites as possible.\nSet the number of items per page to 500\nClick ‘Next’ to go to the second page of the property pane\nClick ‘Custom’ Template and then {}\nReplace this CSS\n/* Insert your CSS overrides here */ .example-themePrimary a { color: {{@root.theme.palette.themePrimary}}; } .site-logo { width: 35px; margin-right: 10px; } .icon { width: 20px; height: 16px; } ul.template–custom { list-style: none; padding-left: 5px; columns: 4; } ul.template–custom li { display: flex; padding: 8px; } .site-link { line-height: 20px; } .site-link \u0026gt; a { display: flex; align-items: center; } .site-link, .site-link a, .site-link a:visited, .site-link a:hover, .site-link a:link { color: #000 !important; text-decoration: none; }\n/* Insert your CSS overrides here */ .example-themePrimary a { color: {{@root.theme.palette.themePrimary}}; } .site-logo { width: 35px; margin-right: 10px; } .icon { width: 20px; height: 16px; } ul.template--custom { list-style: none; padding-left: 5px; columns: 4; } ul.template--custom li { display: flex; padding: 8px; } .site-link { line-height: 20px; } .site-link \u0026gt; a { display: flex; align-items: center; } .site-link, .site-link a, .site-link a:visited, .site-link a:hover, .site-link a:link { color: #000 !important; text-decoration: none; } Next replace the item template\n{{#\u0026gt; resultTypes item=item}} {{!– The block below will be used as default item template if no result types matched –}} \u0026lt;a href=”{{slot item @root.slots.SiteUrl}}”\u0026gt; \u0026lt;img class=”site-logo” src=”{{slot item @root.slots.SiteUrl}}/_api/siteiconmanager/getsitelogo?type=’1′”/\u0026gt; {{slot item @root.slots.Title}} {{/resultTypes}} \u0026lt;template id=\u0026#34;content\u0026#34;\u0026gt; {{#\u0026gt; resultTypes item=item}} {{!-- The block below will be used as default item template if no result types matched --}} \u0026lt;li class=\u0026#34;site-link\u0026#34;\u0026gt; \u0026lt;a href=\u0026#34;{{slot item @root.slots.SiteUrl}}\u0026#34;\u0026gt; \u0026lt;img class=\u0026#34;site-logo\u0026#34; src=\u0026#34;{{slot item @root.slots.SiteUrl}}/_api/siteiconmanager/getsitelogo?type=\u0026#39;1\u0026#39;\u0026#34;/\u0026gt; \u0026lt;span class=\u0026#34;site-name\u0026#34;\u0026gt;{{slot item @root.slots.Title}}\u0026lt;/span\u0026gt; \u0026lt;/a\u0026gt; \u0026lt;/li\u0026gt; {{/resultTypes}} \u0026lt;/template\u0026gt; Click ‘Save’\nClick Republish\nYour page should now look something like this\nConclusion # This is the first step toward making a Site Directory with all the sites you have access to. You could easily extend this to use a search box to filter these items down or change the KQL query to show only certain types of sites. You might also notice that the sites are not in order. In the next article I’ll walk you through the process of implementing the sort on the Site Name.\n","date":"Jan 20, 2025","image":"/images/build-a-site-directory-with-pnp-search-web-parts/build-a-site-directory-with-pnp-search-web-parts-f56dffd1_hu_28388860c59e634a.webp","permalink":"/2025/01/20/build-a-site-directory-with-pnp-search-web-parts/","section":"Post","summary":"This article will demonstrate how PnP Search Web Parts can be used to build a comprehensive Site Directory that not only enhances navigation but also…","title":"Build a Site Directory with PnP Search Web Parts","type":"posts"},{"content":"Applying a consistent site template across newly created SharePoint sites is essential for maintaining uniformity, particularly for departmental sites. While designing and creating templates is straightforward, the application process—requiring manual site visits or PowerShell script executions—is less efficient. This gap underscores the need for automation, eliminating the need for manual interventions or technical scripting. In this article, we introduce a streamlined method to automate site template applications, ensuring consistency and efficiency across your SharePoint environment.\nLeverage Power Automate for Seamless SharePoint Site Template Applications\nUnlock the power of automation with Power Automate by setting up a new flow using a SharePoint administrator account. Here’s how to streamline your process:\nInitiate your Flow: Select the ‘When an item is created’ SharePoint trigger to start your automation whenever a new site is created. Configure Site Address: Input the custom URL of your Admin site in the format https://{domain}-admin.sharepoint.com to specify where your flow should monitor for new site creations. Specify the List Name: Enter DO_NOT_DELETE_SPLIST_TENANTADMIN_AGGREGATED_SITECOLLECTIONS as the list name. This special list captures all newly created sites, ensuring no site, including those associated with Microsoft Teams, is overlooked. By following these steps, you establish a foundation for applying templates automatically to every new SharePoint site, including those created for Microsoft Teams, enhancing consistency and efficiency across your digital workspace.\nStreamlining Site Template Application with Power Automate and Azure Functions\nOnce a new SharePoint site is created, the flow initiates, albeit with a slight delay. This delay is a small trade-off for the automation benefits you gain. At this stage, your flow can send a message to a queue, including crucial site details such as the ‘SiteUrl’.\nImportant Note: The activation of this flow is not immediate. There may be a brief waiting period before the flow triggers. Despite this, it remains the most efficient automation method I’ve encountered for this purpose.\nNext Steps: Leveraging Azure Functions\nThe automation journey continues with the creation of an Azure Function, specifically designed to respond to queue messages. This function, triggered by the queue, grants you the power to programmatically adjust your SharePoint site, including the application of the desired site template.\nFor guidance on creating an Azure Function to apply a site template, please consult the Microsoft documentation. This resource provides comprehensive steps and best practices:\nMicrosoft Documentation on Creating Azure Functions for Site Templates\nThis link directs you to detailed instructions for leveraging PnP provisioning with Azure Functions, facilitating the application of site templates in SharePoint.\n","date":"Feb 20, 2024","image":"/images/sharepoint-site-automatically-apply-site-templates-on-site-creation/sharepoint-site-automatically-apply-site-templates-on-site-c-16f2321c_hu_6b5eabb424e4748f.webp","permalink":"/2024/02/20/sharepoint-site-automatically-apply-site-templates-on-site-creation/","section":"Post","summary":"Applying a consistent site template across newly created SharePoint sites is essential for maintaining uniformity, particularly for departmental sites. While …","title":"SharePoint Site – Automatically Apply Site Templates on Site Creation","type":"posts"},{"content":"I’ve been working on an SPFx web part that allows users to edit content on the page without being a Contributor on the site. It’s basically a remake of the Out of the Box Text webpart but putting it all together had some interesting challenges that I want to share.\nThe focus of this blog is not on the exact solution for the text editor but on how the web part calls an Azure function to update it’s properties AND make the information that is displayed searchable.\nWhat are the major problems? # The users using this web part are visitors. Visitors cannot update edit pages or list items. This web part allows them to edit the content of the web part BUT they can’t actually edit the page and they can’t actually edit update page content or list items.\nThis is why we need to incorporate the Azure Function. The Azure Function will perform the update using elevated privileges. This allows visitors to edit content on the page without having rights to do so normally.\nArchitecture # Below is the architecture used in the scenario. The Azure Function in this case is using a Azure AD app permissions, Sites.ReadWrite.All and authenticating via a certificate to access the SharePoint site and make the necessary changes.\nSPFx Web Part # On the client side there is a standard SPFx web part that will call an Azure Function with a POST request and a body. This body will contain the content of the web part text. In order for the web part to maintain this information it needs to store this in the Web Part Properties. The web part contains a Web Part Property called Content that is a string value. This is what the web part displays.\nAzure Function – PnP PowerShell # using namespace System.Net # Input bindings are passed in via param block. param($Request, $TriggerMetadata) # Write to the Azure Functions log stream. Write-Host \u0026#34;PowerShell HTTP trigger function processed a request.\u0026#34; $cert = \u0026#34;$($TriggerMetadata.FunctionDirectory)\\\\cert.pfx\u0026#34; $cert_password = (ConvertTo-SecureString -String $env:CERT_PASSWORD -AsPlainText -Force) # Parameters for the Webpart update $SiteURL = $Request.Query.SiteURL $Page = $Request.Query.Page $WebPartIdentity = $Request.Query.WebPartIdentity $PropertyKey = $Request.Query.PropertyKey $PropertyValue = $Request.Query.PropertyValue $v = (Get-Module \u0026#34;PnP.PowerShell\u0026#34;).Version Write-Host $v $SiteURL = $Request.Body.SiteURL if (-not $SiteURL) { $errorMessage = \u0026#34;SiteURL parameter can not be empty. \u0026#34; } $Page = $Request.Body.Page if (-not $Page) { $errorMessage += \u0026#34;Page parameter can not be empty. \u0026#34; } $WebPartIdentity = $Request.Body.WebPartIdentity if (-not $WebPartIdentity) { $errorMessage += \u0026#34;WebPartIdentity parameter can not be empty. \u0026#34; } $PropertyKey = $Request.Body.PropertyKey if (-not $PropertyKey) { $errorMessage += \u0026#34;PropertyKey parameter can not be empty. \u0026#34; } $PropertyValue = $Request.Body.PropertyValue if (-not $PropertyValue) { $errorMessage += \u0026#34;PropertyValue parameter can not be empty. \u0026#34; } Write-Host \u0026#34;Request Data is ... \u0026#34; if (-not $errorMessage) { try { Connect-PnPOnline -Url $SiteURL -ClientId $env:APP_CLIENT_ID -CertificatePath $cert -CertificatePassword $cert_password -Tenant $env:APP_TENANT_ID Write-Host \u0026#34;Successfully connected\u0026#34; $web = Get-PnPWeb $webTitle = $web.Title Write-Host \u0026#34;Web: $webTitle\u0026#34; $page = Get-PnPPage -Identity $page.Substring($page.LastIndexOf(\u0026#34;/\u0026#34;) + 1) $controls = $page.Controls | Where-Object { $WebPartIdentity -eq $_.Title -or $WebPartIdentity -eq $_.WebPartId -or $WebPartIdentity -eq $_.InstanceId } $controls | ForEach-Object { Write-Host \u0026#34;Updating web part, Title: \u0026#34; $($_.Title) \u0026#34;, InstanceId: \u0026#34; $($_.InstanceId) try { $webpartJsonObj = ConvertFrom-Json $_.PropertiesJson if ($PropertyKey -and $PropertyValue) { # Check if both PropertyKey and PropertyValue are arrays of the same length if ($PropertyKey.Count -eq $PropertyValue.Count) { for ($i = 0; $i -lt $PropertyKey.Count; $i++) { $webpartJsonObj | Add-Member -MemberType NoteProperty -Name $PropertyKey[$i] -Value $PropertyValue[$i] -Force } } else { # Handle the case where the arrays have different lengths $errorMessage = \u0026#34;PropertyKey and PropertyValue arrays must have the same number of elements. $($PropertyKey.Count) $($PropertyValue.Count)\u0026#34; } } else { $errorMessage = \u0026#34;PropertyKey and PropertyValue parameters must be provided as arrays.\u0026#34; } if (-not $errorMessage) { $_.PropertiesJson = $webpartJsonObj | ConvertTo-Json Write-Host \u0026#34;Web part properties updated!\u0026#34; -ForegroundColor Green $body = \u0026#34;Web part properties updated!\u0026#34; } else { Write-Host $errorMessage -ForegroundColor Red $body = $errorMessage } } catch { $errorMessage += \u0026#34;Failed updating web part, Title: $($_.Title), InstanceId: $($_.InstanceId), Error: $($_.Exception)\u0026#34; Write-Host \u0026#34;Failed updating web part, Title: $($_.Title), InstanceId: $($_.InstanceId), Error: $($_.Exception)\u0026#34; } } # Save the changes and publish the page Write-Host \u0026#34;Saving Page\u0026#34; $page.Save() $page.Publish() Write-Host \u0026#34;Saved Page\u0026#34; try { $item = Get-PnPListItem -Id $page.PageListItem.Id -List $page.PagesLibrary.Id -Fields \u0026#34;CanvasContent1\u0026#34; $canvasContent = $item[\u0026#34;CanvasContent1\u0026#34;] # Load the HTML document using AngleSharp $parser = [AngleSharp.Html.Parser.HtmlParser]::new() $document = $parser.ParseDocument($canvasContent) # Define the target element selector $targetElementSelector = \u0026#34;div[data-sp-webpartdata*=\u0026#39;$($WebPartIdentity)\u0026#39;] div[data-sp-htmlproperties=\u0026#39;\u0026#39;]\u0026#34; # Find the target element $targetElement = $document.QuerySelector($targetElementSelector) $index = $PropertyKey.IndexOf(\u0026#34;searchableTextString\u0026#34;) if ($index -ne -1) { # Create a new div element $newDiv = $document.CreateElement(\u0026#34;div\u0026#34;) $newDiv.SetAttribute(\u0026#34;data-sp-prop-name\u0026#34;, \u0026#34;searchableTextString\u0026#34;) $newDiv.SetAttribute(\u0026#34;data-sp-searchableplaintext\u0026#34;, \u0026#34;true\u0026#34;) $newDiv.TextContent = $PropertyValue[$index] # Remove all child nodes from the target element if ($targetElement.Children.Length -ge 1) { $targetElement.Children.Remove() } # Append the new div element to the target element $targetElement.AppendChild($newDiv) } # Get the updated HTML content as a string $updatedHtmlContent = $document.DocumentElement.OuterHtml # Encode special characters $encodedString = $updatedHtmlContent -replace \u0026#39;{\u0026#39;, \u0026#39;{\u0026#39; -replace \u0026#39;}\u0026#39;, \u0026#39;}\u0026#39; -replace \u0026#39;:\u0026#39;, \u0026#39;:\u0026#39; -replace [char]0x00A0, \u0026#34; \u0026#34; # Update the CanvasContent1 property with the encoded string $item[\u0026#34;CanvasContent1\u0026#34;] = $encodedString $item.SystemUpdate() Invoke-PnPQuery } catch { $status = [HttpStatusCode]::BadRequest $errorMessage += $_.Exception.Message Write-Host $_.Exception.Message } Write-Host \u0026#34;$($_) saved and published.\u0026#34; -ForegroundColor Green $body += \u0026#34; Page saved and published.\u0026#34; $status = [HttpStatusCode]::OK } catch { $status = [HttpStatusCode]::BadRequest $errorMessage += $_.Exception.Message Write-Host $_.Exception.Message } } if (-not $errorMessage) { $message = @{ message = $body | ConvertTo-Json -Compress } } else { $message = @{ message = $errorMessage | ConvertTo-Json -Compress } $status = [HttpStatusCode]::BadRequest } # Associate values to output bindings by calling \u0026#39;Push-OutputBinding\u0026#39;. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $status Body = $message | ConvertTo-Json -Compress }) Issue – Updating Web Part Properties from the backend (PnP PowerShell) won’t make the content searchable\nThere is a bit of magic that happens when you edit a page through the UI. When you edit the page and publish, SharePoint will update a page property called CanvasContent1. In this property it contains web part properties for all web parts on the page as well as the content of the webparts. This holds key information in searchable text strings. All this magic happens when you use the product as designed but when you make an update via PnP PowerShell the CanvasContent1 will not be automagically updated.\nThis sample code demonstrates a method to update the web part and render a new CanvasContent1. By creating a new CanvasContent1 your content would now be searchable.\n","date":"Jan 6, 2024","image":"/images/updating-web-part-properties-via-azure-function-powershell-pnp-searchable-content/updating-web-part-properties-via-azure-function-powershell-p-87a93a3c_hu_f57192569cd68d7.webp","permalink":"/2024/01/06/updating-web-part-properties-via-azure-function-powershell-pnp-searchable-content/","section":"Post","summary":"I’ve been working on an SPFx web part that allows users to edit content on the page without being a Contributor on the site. It’s…","title":"Updating Web Part Properties via Azure Function \u0026 PowerShell PnP + Searchable Content","type":"posts"},{"content":" Intro # In the previous article, we discussed creating a basic JSON-formatted API endpoint in Sessionize to integrate your event content, including Speakers, Sessions, and Schedule, into your Community Days event listing.\nIn this article, we’ll delve into each section in more detail and examine how it affects your event listing on Community Days.\nRead First: Creating a Sessionize Key for Community Days\nSection Highlights:\nSchedule Grid \u0026amp; Session Configuration Hybrid / Virtual – Event Live Links Session Filter Fields / Submission Fields Service Sessions Speakers / Social Links Featured / Top Speakers Sessionize API Endpoint # Let’s get started – in the following sections we cover the items near the bottom of the page, more specifically the Schedule Grid, Sessions, and Speakers options.\nSchedule Grid \u0026amp; Sessions Section # RECOMMENDATION: keep these sections in sync with each other\nParts of the API utilize the Schedule Grid, while others rely on Sessions. To ensure the most consistent experience, maintain the same options for both sections.\nNOTE: Your submission fields will be specific to your event. Jump to the Submission Fields / Filter Fields section for more info.\nFor Hybrid / Virtual events # Check Live icon with Link – this will add the link of the session in Community Days for easy access Video Recording Link [optional if available] NOTE: Live links will be displayed only before the event; they won’t be shown once the event is over.\nResult # In Community Days, you can find this on the Sessions Tab.\nIn Community Days, you can access this on the Speakers Tab.\nIn Community Days, you can find this information on the Schedule Tab.\nAdding Live Links # You can add Live Links to either your Sessions directly or to Rooms. Adding Live Links to Rooms will automatically apply to any Session scheduled in that Room.\nRooms Live Stream Links # Result # The result will be the same as in the previous sections. All Sessions scheduled in that Room will receive the Live Stream Link.\nSession Live Stream Links # From the Sessionize left menu, Click Session, then Click the edit button on the Session you wish to add a Live Stream Link to.\nEnter the ‘Live stream link’ and optionally the ‘Video recording link’, then click ‘Save changes’.\nResult # The result will be the same as in the previous sections. That specific Session will now display a Live Stream Link.\nNOTE: If you have added both a Room Live Stream link and a Session Live Stream link, the Session Live Stream link will take priority.\nSubmission Fields / Filter Fields # This section will show the fields that you can filter by on Community Days, Session Tab. Each event would have different options.\nExample: On this call for Speakers page we ask the speakers to provide the Session Format \u0026amp; Track they are applying for.\nOn the API page we want to include Track so that attendees can filter by this option.\nResult # On Community Days, on the event page, Sessions Tab – we now have the option to filter Sessions by Track.\nService Sessions – Registration, Breaks, Lunch, etc.. # Service Sessions are blocks of time allocated for Registration, long breaks, Lunch or non-speaker sessions such as Opening, Closing or Network related sessions.\nRECOMMENDATION: It’s highly recommended to Include Service Sessions on your Schedule. This lets attendees know about expected breaks so that they can better plan their day and you can set expectations in regard to the event timeline.\nAdding Service Sessions # In the left Sessionize Menu – Click Schedule, then Schedule Builder\nClick ‘+ Add Service Session’\nEnter Detailed Information on this Service Session\nAdd Service Session to Schedule\nClick and drag the Service Session into the Schedule\nClick Save Changes\nResult # On Community Days, on the event page, Sessions Tab – we now have the new Service Sessions included.\nOn Community Days, on the event page, Schedule Tab – we now have the new Service Sessions included.\nSpeakers # This section will show how the Speaker options can add detail to your Speaker listing in Community Days\nLinks # Community Days will read in and display the following links:\nTwitter / X LinkedIn Blog Company Website Sessionize Result # On Community Days, on the event page, Sessions Tab or Speakers Tab, clicking a Speaker will show their provided links.\nShow Top Speakers first # Community Days will automatically showcase Top Speakers if they are marked Top Speakers / Featured.\nIt is recommended to check this option to Show Top Speakers first in the mobile App.\nFeature Speakers / Top Speakers # As stated in the previous section, Community Days will always show Top Speakers as Featured Speakers\nHow to Enable a Top / Feature Speaker # From the Sessions left Menu, Click Speakers, then Click on the Edit button on the Speaker you wish to turn into a Top / Featured Speaker\nCheck ‘Is top speaker of this event?’, then Click ‘Save changes’\nResult # The selected Speaker(s) will be displayed at the top of the Speaker Tab in your event listing.\nConclusion # This concludes this guide on the various options in Sessionize and how they tie into Community Days. If you need more help please leave me a comment or head over to CommunityDays.org and fill out our feedback form.\n","date":"Nov 25, 2023","image":"/images/community-days-sessionize-key-deep-dive/community-days-sessionize-key-deep-dive-avocado80_a_sunny_scene-d73de5_hu_4e01a935f05b4132.webp","permalink":"/2023/11/25/community-days-sessionize-key-deep-dive/","section":"Post","summary":"Intro In the previous article, we discussed creating a basic JSON-formatted API endpoint in Sessionize to integrate your event content, including Speakers, …","title":"Community Days / Sessionize Key Deep Dive","type":"posts"},{"content":"In this walkthrough, we’ll guide you through the steps to create a Sessionize key, which is required by Community Days for integrating your event’s Speakers, Sessions, and Schedule.\nCommunity Days leverages the event content you have on Sessionize.com. By using Sessionize.com, Community will handle displaying your event content in a consistent and user-friendly manner.\nBefore we begin, please ensure that you have a Sessionize account and an existing event that has been approved on Sessionize.com.\nQuick Start – Creating JSON Sessionize Endpoint # Click on your event from the event Dashboard\nNext Click on API / Embed\nOn the API Dashboard you should see 1 Default endpoint. If you look at the Format column you will see ‘Styled Html’. This is used to embed into your existing website. Community Days does not support this format. We use the JSON format and in the next few steps we will walk through the process of creating a new key. In the API Dashboard, start by clicking on the ‘Create new endpoint’ button\nNext, provide a name for the endpoint, such as ‘Community Days.’ Then, make sure to check the following options:\nEnabled JSON (coding required) It’s recommended to include Sessions with the status ‘Accepted and Informed and Confirmed.’ This will display speakers and sessions that have been notified that their content was selected and have confirmed the notice. The default value is ‘Accepted and Informed,’ which will show speakers who have not yet confirmed the selection of their sessions.\nAdvanced Options # It is recommended to keep the default options here.\nThis option will only show information on Scheduled sessions. If you do not plan to use Sessionize to build a schedule you may want to change this.\nRecommended Options # If you prefer a quick recommendation, refer to the following image and choose the corresponding options. For more advanced information check out: Community Days / Sessionize Key Deep Dive\nFinishing Up # Click ‘Create endpoint’\nCopy / Paste your Sessionize Key for later use in Community Days and you are all set!\nConclusion # This concludes the basic steps to get your Sessionize content integrated with Community Days.\nIn the next article, we’ll review each section individually and discuss its impact on the display in Community Days.\nNext Article: Community Days / Sessionize Key Deep Dive\n","date":"Nov 25, 2023","image":"/images/creating-a-sessionize-key-for-community-days/creating-a-sessionize-key-for-community-days-avocado80_a_sunny_sky_wi-3235e9_hu_38512490ce95b55c.webp","permalink":"/2023/11/25/creating-a-sessionize-key-for-community-days/","section":"Post","summary":"In this walkthrough, we’ll guide you through the steps to create a Sessionize key, which is required by Community Days for integrating your event’s Speakers,…","title":"Creating a Sessionize Key for Community Days","type":"posts"},{"content":"I’ve been having major troubles when I am deploying my Node.js application. It will often get stuck deploying in the Azure Web App b/c of the oryx build process. This is highly frustrating because it already takes close to 10 minutes to produce the build and deploy the web app. These deployments stick regardless of push via Visual Studio Code or through Azure DevOps Pipeline. They don’t stick all the time but when they do it’s seemingly permanent. In this blog I’ll cover the issue that happens and what you can do to revive it.\nYou’ll know it’s stuck when the deployment takes forever and you check the deployment logs in the Deployment Center and the output is the same each time.\nYou’ll also know it’s stuck if you try another deployment and you get this error\nError Type 409. “Error: There is a deployment currently in progress. Please try again when it completes.”\nOption 1) Delete the web app and recreate it\nIf you’ve done some research on these you’ll see some issues or forum posts on the topic. Most people have just given up and deleted the entire web app and recreate it from a clean slate. This will work but the package will get stuck again and you’ll soon find that it will become too much work to do all that.\nOption 2) Delete the deployment lock files\nI literally just started rummaging around the file system when I noticed the deployments folder with some lock files. I cleared that folder, restarted the web app and was able to redeploy again.\nThese two options will help you get past the stuck in deploying for the current moment. I’m now on the hunt for the cause of the issue. I know this doesn’t happen with the same app deployed to the windows container. It’s somewhat isolated to a Linux container, node.js, and the oryx build. If I figure that out I’ll be sure to post that in a future blog.\n","date":"Feb 12, 2022","image":"/images/azure-web-app-deployment-issues/azure-web-app-deployment-issues-avocado80_a_sky_with_lig-f92533_hu_50c210657aff2d61.webp","permalink":"/2022/02/12/azure-web-app-deployment-issues/","section":"Post","summary":"I’ve been having major troubles when I am deploying my Node.js application. It will often get stuck deploying in the Azure Web App b/c of…","title":"Azure Web App Deployment Issues","type":"posts"},{"content":" Introduction # I love the FontAwesome library and use it on many projects. I was a huge fan of FontAwesome 4.0 and had a little struggle moving over to using 5.0 + 6.0 in my projects. I was use to the old way of including the link to the CSS files and using the class names. You can still do that if you wish but this article shows using the React component approach. It’s simple once you’ve done it a few times.\nInstall React Font Awesome\nnpm i –save @fortawesome/react-fontawesome Install the Core Packages [required]\nnpm i --save @fortawesome/fontawesome-svg-core Install the Icon Packages, use only the ones you need. If you only use solid icons then choose that. If you are not sure right now then you can include them all and remove them later.\nnpm i --save @fortawesome/free-solid-svg-icons npm i --save @fortawesome/free-regular-svg-icons npm i --save @fortawesome/free-brands-svg-icons Go Find a Font – https://fontawesome.com/icons\nKeep in mind that you only have the free library so you can only use those unless you buy the pro license\nIf you pick an icon and it says ‘Start Using this Pro Icon’ then you need to choose something else or purchase a license\nYou can filter the free ones by clicking this on the left.\nI’ve selected the fish icon, next you need to take note of the type ‘Solid’ or ‘Regular’\nNow import the FontAwesomeIcon component and the icon you want from the correct package\nThis fish icon is in the Solid package.\nIf I wanted to use something from the regular package like this icon\nThis font is in ‘Regular’ package you would import it from that package instead\nThere is intellisense inside visual studio to help get the correct name of the icon\nNow the result\nIf you wanted to use the brand icons you would follow the same approach. I believe that all the brands icons are free.\nI love FontAwesome library of icons and use them in many projects. The PRO version unlocks so many more fonts and customizations that it’s worth buying if you plan to use them and want to support this project.\n","date":"Jan 20, 2022","image":"/images/adding-font-awesome-to-spfx-react-project/adding-font-awesome-to-spfx-react-project-0_0_hu_d4bc8bb75cc39a28.webp","permalink":"/2022/01/20/adding-font-awesome-to-spfx-react-project/","section":"Post","summary":"Introduction I love the FontAwesome library and use it on many projects. I was a huge fan of FontAwesome 4.0 and had a little struggle…","title":"Adding Font Awesome to SPFx React project","type":"posts"},{"content":" Introduction # After an event I like to wrap up some of my thoughts on how the event started and some of the lessons learned. This post will be broken out into the two parts of the story on the event and then some of the lessons that come with running an event. For those that don’t know me I run many community events in the NY/NJ/PA area and involved with helping other events either get started or with supporting them by volunteering my skills, time or helping them reach people.\nThe Back Story # Each year I host a local Azure event in New Jersey as part of the Global Azure Bootcamp. It’s a 1 day event held on a Saturday at the local Microsoft office in Iselin, NJ. It’s been a very successful event with about 90 out of a possible 100 that can fit in that office safely. In our area we’ve seen a demand for Azure training and information grow drastically over the past 3 years. Due to the local office being closed for public events we’ve had to spread our wings and take to the cloud.\nThis year was our first virtual Azure event on our own. Alone in the sense of no major support from a company. It would be a grassroots event where we would have to push this out to the public to gather sponsors, speakers and attendees with little visibility / marketing. Events like this cannot happen alone. You need a solid team to help and that’s something that I knew I had in my pocket.\nIt start with a phone call from Rajaniesh Kaushikk who was an attendee at the 3rd Global Azure Bootcamp. He reached out to me and said we haven’t had an Azure event since the pandemic, would it be possible to run one before the end of the year. If it wasn’t for his push, I would probably never have run this event. Raj’s energy had set the spark for me to gather the team. Jason Rivera who runs M365 Philly events like the User Group and M365 Saturdays. Manpreet Singh from the Tech Platform who always helps out with NY/NJ/PA events and many other events within the community.\nThe bootcamp was initially planned to have 4 speakers and short and quick, easy to manage. What would be a small event with a few people, no stress, easy to manage. We put out some feelers but launching a call for speakers on Sessionize.com. We were a bit overwhelmed with the response. Speakers from around the globe were applying to our little event from regular people wanting to share their experience, seasoned professionals, Microsoft Certified Trainers and even Microsoft Azure MVPs. We had 34 sessions and 22 speakers to choose from which included 8 Microsoft MVPs, a handful of C# Corner MVPS, Bloggers, Speakers, YouTubers. Quickly our event gained some momentum at least from the speaker community. We ultimately settled on 18 speakers, although we knew that would be a handful to deal with in just 1 day. We had to have 2 tracks and run longer than we’d like to. It was a virtual event, so our focus wasn’t on just EST time, we knew we’d have audience in attendance from other areas.\nAs I just mentioned the event was planned around EST time. This created some challenges for speakers from around the globe. We had some dedicated speakers! Eric Cheng from Sydney, Australia presented his session from 3am his time. Any many others presenting at 9pm their time. I was blown away by the commitment of the speakers and they were so appreciative of the opportunity to speak at this event.\nRunning the Event # Prep Work # There is a lot of work that goes into these events. There’s a ton that goes into prior to anything you might even see from the outside. I’ll shed some light into some of the major areas.\nTeam – you need a solid team of people to do actual work. You don’t want to end up doing everything yourself. I am known for taking on too many things and doing everything myself. You need to form a team and be able to plan, delegate, track, and keep that ball rolling. This is always challenging in a community ‘free’ event. Watch out for volunteers that simply volunteer to put their name on it. You need grunts and you need influencers. It’s very common for other MVPs to come along and offer to help but those typically aren’t the ones that put in the real work. They might help your event get some notoriety or help with promotions, but they aren’t the ones building the website or making slides [typically]. Get a balanced team – with some people that are motivated to help and do work. If you’re the leader then you need to be sure to lead and delegate. Give people tasks and check in and follow up weekly / bi-weekly.\nGraphics – no event can happen without some great graphics. You need catchy event graphics, speaker promotions, sponsor promotions and more. You could sub this out to fiverr but if you can link up with someone that can use Canva or even better Photoshop then you’ll be set. I used to do all the graphics but for this event I was able to get help from Omer at SoHo Dragon.\nSocial Media Promotion – no event can happen without promotion. You won’t be seen or noticed without a social media presence. We failed in this event to secure an Azure Community Bootcamp twitter profile, so everything went out from my personal account or M365 NYC account. I’m sure people were not thrilled to be getting tons of promotions from my personal twitter and LinkedIn, but I didn’t care that much. Mute me. The point is that you need to get the word out. Involved the speakers, sponsors and organizers in the promotion. It’s everyone’s job. If you can create a game for attendees to promote, then do that as well. Our primary methods of promotion were LinkedIn and Twitter. We posted our event on Eventbrite which has some built in traffic that can also help. We also cross posted on the NJ \u0026amp; PA user groups on Meetup.com. In hindsight I should have reached out across the USA to the Azure meetups. I would have if I had more time.\nTicketing – mentioned above we used Eventbrite for our ticketing which can help with some promotion sales. It takes a good amount of time to setup the Eventbrite for an event. It’s not difficult but you should be asking questions and gathering the correct information of the attendees. You want to think about collecting information if you are sharing with sponsors. Important things like company, location, phone. You must also ask if you can share the information. I highly suggest an option to be added to the mailing list. Building a mailing list is critical for building an audience.\nWebsite – you will need a website. Hopefully someone on your team can make one or you could use a wix.com site. We’d prefer to have a custom site, but we started out with no site. Ultimately, we decided we needed a site to look like a legit event. Legit in the eyes of the world you at least need a website. Not a meetup.com event, not a Eventbrite event. You need a place on the internet that you can see the event, the schedule, the speakers, etc. We went with wix.com because we could have a site up and running within a few hours. Another requirement is that it needs to be mobile accessible. Using wix it was easy enough for volunteers to help update and did not need to know programming languages or anything else. Just use the web interface to make changes. We also used Sessionize embed for the speaker / sessions / schedule which was a bonus since we did not have to build that integration. You can check it out here – https://www.azurecommunitybc.com/\nSpeaker Call – having a formal speaker call allows potential speakers to submit their information to you for evaluation. Sessionize.com works great for FREE events. ** Sessionize is a pay service BUT is FREE for a FREE event. You must apply when you host a free event to get it for free **. You can use the speaker call to bring in speakers. You can customize the form with custom questions. The platform allows you to invite content members to help select speakers. You can accept / deny speakers from the platform easily. You can build the schedule inside the platform with rooms and time slots. You can email out all the speakers from it to communicate event updates. Most importantly you can use all the information from Sessionize to embed into another website. You do not need to recreate the schedule, sessions or speaker lists on your site. You can simply use the embed code to post the information. If your feeling frisky you can even customize the CSS to style it as you wish.\nSponsors Call – Community events I run are free, but we do need sponsors to help. Sponsors help pay for website costs, domain names, email accounts, and some other misc fees. Once you run an LLC for a User Group you’ll see. You need to file taxes, have accounting software and whatnot. Even though I keep those at the bare minimum there are other expenses. Plus, you want to have a great event with some giveaways. Normally the sponsor dollars cover food which is not happening at the moment, so they cover the web hosting fees + giveaways. It’s the truth that someone pays for a free event. I’ve covered many a user group pizza night on my own dime. It’s fine because I am able and willing to do so. You understand when you run a user group. My insight here is keep the sponsorships to a normal amount and make it fair. We didn’t solicit sponsors for money for this event, I just wasn’t prepared to ask for money not knowing the outcome. I felt that big sponsors wouldn’t be interested in such a small event [turned out to not so small]. Our sponsors have donated resources like time from their employees or services. We thank SoHo Dragon and the Tech Platform for their support this year. Both of those sponsors help out with ALL of my events no matter how big or small.\nTech Challenges # Meeting Platform – we chose MS Teams Live Events to run the meetings. There are some pros and cons to this approach.\nPros #1 it’s easy, you run 1 long event for each track and you are done. No need to have attendees drop in and out of sessions. This was a huge draw since we did not have a huge moderator team. #2 you can’t see the live attendees – if there are no attendees the speaker doesn’t know so they don’t act differently. Present without the concept of an audience. We’ve seen / attended and even presented to teams events with 1 or 2 people and it’s just not fun. [not this event but with others]\nCons #1 you can’t see the live attendees – The speaker doesn’t know who is in the session or how many people attend. This can throw speakers off if they plan to have Q/A or interactive sessions. It just doesn’t work in this format without planning [using surveys or forms for questions and circling back], #2 there is a delay – this causes disconnect between the presenter and the audience. If you are asking questions, it just doesn’t work great. It can take a minute or so before the audience hears the question and then replies.\nTech Check – Always do a tech check with MS Teams. Make sure the users can log in and share their screen on the same computer they will do it live. It verifies that the account can get in with no issues. We’ve seen people attend the tech checks and then not able to join because they are using a different computer or different account. We’ve seen people have issues b/c their session is about teams and they need to be logged in to present and also log into teams. They need to use a VM in that case to be part of the live event and show another MS Teams tenant. Also if you get any last minute speakers always do a tech check [got us this time]. We’ve seen people use gmail accounts and then have difficulties logging into MS Teams because it needs a MS based account like hotmail/outlook.\nTime Check – send out the schedule in your time zone. Don’t depend on the web or Sessionize to convert it correctly. We had some speakers at the wrong time because they saw online at some point it say something else. Make sure to send the print out / email of the schedule with the time in your time zone.\nMultiple Presenters – MS Teams multiple presenters is a problem. One event I moderated had 3 presenters that I would be constantly changing the web cam for. Teams can only show 1 webcam at a time. This is a problem for us in the wrap up session. We want to have all of our organizers on to say their last words about the event. We use streamyard.com to create a live event on youtube and forward our attendees there for the last sessions [the wrap up]. We still don’t know who shows up but it allows up to wrap up the event with a little behind the scenes footage. We give our take and thank everyone involved. Sometimes we raffle items off in there as the close out and if not we just use that as the organizer platform to speak our minds.\nCommunication – over communication is never a problem. Stay in touch frequently with your speakers and attendees. Let them know about the event at least once a week when you are in the final month. Attendees need to know how to easily access the sessions. Give them multiple ways to get in – post is in the email, use the web, blast it out on social media. It’s never a problem to over communicate how to attendee the event. Same with the speakers – let them know about event changes with the schedule or format. Give them access to a OneDrive folder with the content for promotion materials, slide decks or whatever else you might have for them.\nModerators – have enough moderators for each session. The purpose of the moderator is the welcome the speaker and inform them of any last minute changes, how to share screen, let the speaker know that nothing is sharing, how to turn on subtitles and remind them when time is up and to show the event slides / feedback slide. Have a dedicated moderator for each session and 1 backup so that people can switch out. Never let someone moderate [alone] that hasn’t moderated before. Put a moderator schedule out ahead of time so everyone can confirm that they will be on the watch for their session.\nPhysical Challenges # Time Management – running events is a full day affair. If your involved, you’ll likely have to take off work. There’s no room to concentrate on the event and try to take a meeting. That means be aware of business owners / CEOs or CTOs on your team. They would likely get pulled away with some emergency. You need reliable sources to help the event.\nReal Life – If you have kids let the team know when you won’t be available because you’ll need to focus if you want to do a proper job. My kids are young, so they don’t really listen when I say play quietly. To them that means check out who’s on the camera, draw behind me on the whiteboard, rip my office apart, play with loud toys or just fight because I can’t stop them. They might have made an appearance in this event.\nBio Breaks – you can’t be moderating all sessions on two computers. Sure, it’s possible for a short time but not all day. Make sure you have back up and let the speakers know that you’ll be in the session to help out but popping in and out every 10 minutes. This gives you are free minute to use the bathroom or stretch your legs. It’s impossible to sit at the computer the entire day without leaving. You need to program in some breaks for your person.\nSummary # What a day, what a day. The experience of running a virtual event doesn’t even come close to a live event. Live events you get that instant satisfaction of meeting people and hearing about their experience. Virtual events you get to read about it from the few that bother to fill out our the feedback form. If you know me then you know I’m not a huge fan of virtual events, but I know that they are important to keeping the community alive during this time. Despite some of our challenges above I would consider the 2021 Azure Community Event a success. We did it, we held a unique online event that reached 100’s of people and I’m happy with that. We held an event that hosted 18 speakers of high caliber, with 2-3 moderators, 4 organizers, no monetary sponsorships, 1 day, 10 hours, 2 tracks, planned within 2.5 months.\nYou can visit the event website here – https://www.azurecommunitybc.com/ . We’ll be posted the recordings from the event within the next week. Leave me a comment, let me know if you have questions about running an event in your area or online.\n** At the time of this writing I have not yet read any event feedback from our attendees. **\n","date":"Nov 20, 2021","image":"/images/azure-community-bootcamp-2021/azure-community-bootcamp-2021-avocado80_in_the_distanc-18e5cc_hu_24551c3d5ec3d99f.webp","permalink":"/2021/11/20/azure-community-bootcamp-2021/","section":"Post","summary":"Introduction After an event I like to wrap up some of my thoughts on how the event started and some of the lessons learned. This…","title":"Azure Community Bootcamp 2021","type":"posts"},{"content":" Introduction # This new project is a nice little feature that you can easily add to your SharePoint site to enable site messages or alerts. Having the ability to display site alerts is one of the top 10 most requested features to enhance SharePoint. This customization is built as an SPFx application customizer, with a little bit of UI hacking. This project can be used as-is or as a starting point for you to extend and create your own combinations of notifications, icons and colors.\nGit Repo Pre-Build SPPKG Overview # The Alerts Header solution contains an SPFx application customizer and a list schema for the alerts messages. The list will automatically be created for you when the app is added to your site. Currently the app displays messages from the site it’s in but could be easily modified to pull notifications from another site. Initially this was designed to be deployed the the entire intranet and activated on each site. Each site had the option to display ‘local’ alerts, alerts contained within the site and remote alerts, alerts contained at the root site collection “/”. The remote alert functionality has been commented out for simplicity in the Git Hub repo but could be reenabled with just a little effort.\nImplementation # The List # The solution installs an ‘Alerts’ list in your site. This list contains a number of required fields to properly display the alerts.\nTitle – The first part of the text that is displayed\nDescription – The second part of the text that is displayed. The description will fill the reminder of the space and truncate with ‘…’\nAlert Type – The icon that is displayed. Warning, Actionable or Info as show below from top to bottom.\nLink – The navigation url and the text that will be displayed\nStart Date / Time – The date and time that the alert will be displayed\nEnd Date / Time – The date and time that the alert will no longer be displayed\nItems will immediately start to show up once they are added to the list and hit the duration between the start and end date.\nThe Web Part # This view below shows the alerts application customizer with all three types of alerts and active.\nIcons – The icons used are from the Font Awesome 5.0 free library. You could easily update the code to change out the icons.\nExpand / Collapse – If the alert has a long description it will be truncated once it fills the available screen space. The up / down arrow will allow the user to expand and collapse the alert to read the full description.\nDismiss / Close – The alert has and close icon at the far right to dismiss the alert. The alert will no longer show during that session. A new session will display the alert again. New Tabs are equivalent to new sessions.\nSharePoint UI Hack – I hinted earlier that I’ve included a bonus UI Hack for free, haha. Take a peek at the picture below. The normal location of an application customizer is at the very top of the page. I did not want the alerts to show up there so I opted to move them to the bottom of the header. This is a non supported customization according to Microsoft. I’m pointing this out for transparency.\nResponsive / Mobile Friendly – The web part was designed with the intention of working across all devices and screen sizes. The functionality is somewhat different on the mobile. Once the item is expanded it can only be closed. I’ve do it this way to intentionally close them out to regain the screen real estate back on the small devices.\nCustomizations # The web part comes with a few notification types. You can add more types directly to the list or to the list schema. After you add new Alert Types you would then need to do a few more updates\nIAlerts.types.ts – Add your new Alert Types to the enumeration\nAltertItem.tsx – Add new conditions for the new Alert Types and the icon you want to use\nAlertItem.module.scss – Copy / Paste an existing Alert Type combination and change the colors to meet your needs.\nConclusion # Site Alerts are one of the top 10 features requested from clients and customers to build. I’m happy to be able to post this feature to share even though it’s not 100% Microsoft compliant. The tiny UI hack positions this feature in the location where I felt it belongs. I hope and wish that Microsoft would give us more Zones in which we could target in the application customizers.\n","date":"Oct 10, 2021","image":"/images/alerts-header-spfx-project/alerts-header-spfx-project-avocado80_a_picture_of_a-eed22d_hu_6fcfc72c64a8c23e.webp","permalink":"/2021/10/10/alerts-header-spfx-project/","section":"Post","summary":"Introduction This new project is a nice little feature that you can easily add to your SharePoint site to enable site messages or alerts. Having…","title":"Alerts Header – SPFx project","type":"posts"},{"content":"Awarded Office Developer MVP 2020-2021 – I am thrilled to announce that I was award a new title of Office Developer MVP. I’ve been a Office Apps and Services MVP for 4 years and have been contributing to community and helping others long before my first award in 2017. My category changed from Office Apps and Services to Office Developer this past award. Mostly due to my involvement with more github contributions, feedback to the SharePoint developer product teams and implementing SPFX bootcamps in NY, NJ and PA.\n","date":"Aug 14, 2021","image":"/images/awarded-office-developer-mvp-2020-2021/awarded-office-developer-mvp-2020-2021-0_0-1_hu_89a3ccce4efa2e.webp","permalink":"/2021/08/14/awarded-office-developer-mvp-2020-2021/","section":"Post","summary":"Awarded Office Developer MVP 2020-2021 – I am thrilled to announce that I was award a new title of Office Developer MVP. I’ve been a…","title":"Awarded Office Developer MVP 2020-2021","type":"posts"},{"content":"On June 8, 2021 I passed my AZ-204 exam to become a Microsoft Certified Azure Developer Associate. I completed the exam on my first attempt after a month of studying. The majority of my knowledge came from working with Azure and developing over the past year. There are many free resources that are also available on youtube and Microsoft that were also helpful. Nothing beats having hands on, real world experience in preparation from these exams. This exam was not so easy as it requires to recall commands and code snippets from memory. I’m excited that I was able to pass and looking forward to continuing to develop my Azure knowledge and understanding.\n","date":"Aug 14, 2021","image":"/images/passed-az-204-developing-solutions-for-microsoft-azure/passed-az-204-developing-solutions-for-microsoft-azure-0_0_hu_9637c43c8819e1fc.webp","permalink":"/2021/08/14/passed-az-204-developing-solutions-for-microsoft-azure/","section":"Post","summary":"On June 8, 2021 I passed my AZ-204 exam to become a Microsoft Certified Azure Developer Associate. I completed the exam on my first attempt…","title":"Passed – AZ-204 Developing Solutions for Microsoft Azure","type":"posts"},{"content":" Introduction # This post will demonstrate another SharePoint UI hack that is typically seen on many modern sites today. Back to top is a button that will position itself on the right hand bottom corner of the screen once you scroll a bit down. It’s very useful on long pages where your users might want to scroll up quickly and not get mouse scroll rash on their fingers in the process. This is another client request that I hear infrequently but I found this as something that the general population can implement and learn from.\nBefore you continue, I must mention that SharePoint UI hacks are unsupported and frowned upon by Microsoft as we cannot rely on the UI [html, classes and DOM] to remain the same over time. With that said you are free to implement and learn more about SPFX and the current DOM given this example. When Microsoft implements a change this would potentially stop working and that is all. Post an issue on github and I would be happy to review and make a change.\nGit Repo – Back to Top Pre-Build SPPKG Overview # I’m not going to go into the nitty gritty details of the code in this article, you can look at the code for that. This customization is based on an SPFX application customizer. This application customizer injects a button when the users scrolls past a certain point. The button would then jump the user back to the top of the scroll region.\nThe scroll region might change at some point for SharePoint, rendering this customization ineffective.\nImplementation # In the SPFX application customizer there is some code to detect the scroll event. There is also some code to detect the page navigation event as it’s somewhat tricky. When users navigate away from a page application customizers are not re-rendered. There is also some code to detect a navigation event and refresh the scroll area so that this SharePoint UI hack continues to work.\nThe current scroll region is defined in the BackToTop.tsx. The current scroll region that is the target can be seen below.\nThe customization is also theme aware and will respect the primary color of the theme.\nInstallation # As with many github solution you can always clone the repo and make any changes you desire to the solution, check out the REPO. Alternatively you can take the latest build and install it into your app catalog.\nOnce you install the solution into your app catalog it will be available as an App on your site. It’s as simple as that.\nSummary # I hope you enjoy this, yet another SharePoint UI Hack. It’s a great little enhancement if you have long content and want to scroll back up to the top of the page quickly. Feel free to use the github code to inspect how it’s implemented, make changes or enhancements or just implement it as the package is available for you. I’ll mention again that SharePoint UI hacks are not supported by Microsoft so don’t ask them for support. If you implement this you should always remove it before troubleshooting other issues. Generally it’s safe to apply as it won’t adjust or change anything permanently within your site, but take note that if there is a change with the SharePoint UI it could stop working as some point in the future. Please feel free to reach out or comment if your feedback or experience.\n","date":"Aug 14, 2021","image":"/images/sharepoint-spfx-back-to-top/sharepoint-spfx-back-to-top-0_2_hu_c689286d1d7b7f4d.webp","permalink":"/2021/08/14/sharepoint-spfx-back-to-top/","section":"Post","summary":"This post will demonstrate another SharePoint UI hack that is typically seen on many modern sites today. B","title":"SharePoint – SPFX Back to Top","type":"posts"},{"content":"I’m posting this quick tip on how to create a site collection based app catalog with PowerShell. I used this over and over when I’m developing and I think it will be helpful for many people to have a quick script to be able to execute.\nI’m using the SharePoint Online Management Shell for this script. You simply need to replace the domain of your tenant and execute.\n#configuration $domain = \u0026#34;YOURDOMAIN\u0026#34;; #{domain}.sharepoint.com $targetSite = \u0026#34;your site\u0026#34;; #https://{domain}.sharepoint.com/sites/{targetSite} #script $siteUrl = \u0026#34;https://$domain.sharepoint.com/sites/$targetSite\u0026#34; $adminSiteUrl = \u0026#34;https://$domain-admin.sharepoint.com\u0026#34; Connect-SPOService -url $adminSiteUrl $site = Get-SPOSite $siteUrl Add-SPOSiteCollectionAppCatalog -Site $site Enjoy!\n","date":"May 30, 2021","image":"/images/create-site-collection-app-catalog/create-site-collection-app-catalog-avocado80_a_library_book-62778c_hu_9239c931567c72e5.webp","permalink":"/2021/05/30/create-site-collection-app-catalog/","section":"Post","summary":"I’m posting this quick tip on how to create a site collection based app catalog with PowerShell. I used this over and over when I’m…","title":"Create Site Collection App Catalog","type":"posts"},{"content":"This past year has been a challenging year for running events. For those that do not know me, I am someone that facilitates many events in the local New Jersey, Pennsylvania, New York area [tri-state]. I must admin that I struggle with not being able to host physical events during the pandemic. I’ve been able to meet so many amazing people traveling within the local area. I’ve also developed friendships and professional relationships that I would almost never had made through online events. The immediate satisfaction of executing a live event with hundreds of people still doesn’t match a virtual event of the same magnitude. With that said the virtual events are still satisfying to host and they bring a whole different set of challenges over an in person event. The point of this article is to review insights into hosting a virtual event are cover the pros and cons.\nFor the past 10 years or so I’ve been somewhat known for hosting and helping out with events in NJ and NYC at the local Microsoft offices. I’ve hosted SPFx bootcamps, both Office 365 Developer and Azure Global Bootcamps, and last but not least SharePoint Saturday NYC + NJ aka O365 NYC aka M365 NYC. These events in New York have had upwards of 900 registered users to attend and typically see 300-400 people in person, and on a good year peaking 500 people in person. Amazing to be able to see this event unfold and the amount of planning and preparation on the organizer partnering with the Microsoft staff is truly a spectacle.\nHere we are now in the virtual event era where all we can have is virtual events. Running events in person have certain challenges in terms of budget, location, logistics and come with risks of getting speakers, no shows, cancellations, timing. Virtual events as they seem somewhat easier… they are not.\nOrganizers – The backend planning crew. It takes a few people to be the heart and soul behind the event in order for it to actually happen. In the case of the Philly crew it was Jason Rivera, Mike Mukalian, Manpreet Singh and myself. With the late addition of Dave Leville. We all have different responsibilities which make the event happen. I’ll outline the roles of our Organization team below.\nJason Rivera – Main Organizer aka Project Manager – runs the user group in PA and starts the process. The Heart of the operation. Jason has the pulse of the local area and drives the month to month operation of local events [virtual for now]. Jason is the key point of contact with the local community and also ensures that the best interest of the local community is upheld with all decisions that are made.\nManpreet Singh – The Tech Platform – Runs The Tech Platform and is very comfortable with running online Teams events. Manpreet is also a key part of events running in the NJ/NYC/PA area, Current host of the NJ M365 User Group. No stranger to online events and know how to run things in MS Teams and cross platform like YouTube/ FB Live. Manpreet was always in the background supporting our producers in the event that anything went wrong.\nMichael Mukalian – Microsoft Contact – the blue badge. Loads of knowledge and contacts. Critical role to have on the team to ensure that the event runs inline with Microsoft’s vision aka best practices aka the direction of the modern workspace. Michael has always been involved in the Philly community for many years and his years of dedication to fostering events is essential.\nThomas Daly [myself] SoHo Dragon – Support – My role was a support role such as getting the word out for speakers to apply, getting producers, talking to sponsors, assisting in voting of topics, being a producer and any last minute things that fall through the cracks. The years of experience and relationships in the community play a vital role in my ability to provide support.\nDavid Leveille – Crush Networks – Marketing / Support – David not just a typically sponsor. David was a late addition but nonetheless a crucial role in help us spread the word for the event. David brought in both speakers and sponsors attention to our event. David was also key in the marketing aspect of our event as he has experience in mastering the social platforms to raise awareness and visibility to outside of our respective reach. David is also key persona for other M365 events such as M365 Virtual, California, Idaho, Mexico\nProducers – Your frontline workers. Having the correct number of producers to help with welcoming the speaker, kicking off the sessions, controlling the timing so session do not run over, facilitating questions from the audience in a controlled manner. Depending on the number of tracks / sessions you need to have full coverage with dedicated producers to be your eyes and ears in the channels. They will report back any major problems during the sessions.\nShout out to our Producers: # Jaime Willis-Rose – Applied Information Sciences – Super Star Power Platform Developer – Jaime is a super star, always willing to step in last minute and help out. Rockstar in my book. Tosha Desai – Co worker at SoHo Dragon – A dedicated colleague and expert in the M365 realm. Jumping in the last minute to help to produce our Friday Sessions. Stacy Burris – M365 Organizer – former director of SPTechCon now PAIT Group Marketing \u0026amp; Operations Director – Stacy always goes above an beyond when she can. She helped produce a few of our Saturday Session for this event. Nishant Mohammed Schedule # We put all of our sessions on the website through rebrand.ly. This is in case someone accidentally closed out the Teams Live event. If they close the Teams Live event we would have to recreate a link for a new event and have our attendees access that. rebrand.ly was our vanity url service so in the event that a Teams Live event was accidentally close we could have minimal down time. We could quickly change the link behind the scenes and users would experience very little impact.\nTeams Live Events / Teams Events # The big question is why Teams Live events vs Teams Events. This seems to be the biggest topic for discussion. Teams Live events we scheduled for the entire day so we had a consistently running event. We would later cut the events down for the recordings in post production. In a Teams Live event the speakers can join externally and they don’t need an account as long as they were added to the Team in advance. However some speakers have an issue with this but apparently not so much this time. We had tech checks in place to verify that speakers could join and present.\nWe could not run Teams meetings last year because there was not option to mute all attendees. This was since correct by the MS Teams team and we could control the audience with a typical teams meeting. However we did take into consideration that M365 Virtual Marathon had some comment spam during these events.\nThe down side is that our speakers do not know how many people are in the sessions at a given time. There is also a delay for Q/A close to 30 seconds to 1 minutes. This greatly impacts the interactive nature of the conversation. We can’t tell who is there and it’s a little weird to talk to no-one. You might be there but the speakers can’t tell.\nCo Presenters # We had a few presentation where there were 2 or more presenters and that was a challenge b/c only 1 screen and 1 video could be produced at a time. I had a track Winning Combination: Team up with the Microsoft 365 PnP Community and become a SharePoint All-Star with David Warner, Beau Cameron, Hugo Benier and Chris Kent that I had to juggle the speaker video as they were talking. This was not necessarily a problem but also a consideration for live events where I had to switch up the speakers video feed during the talk.\nFeedback # Feedback is one of the most overlooked aspects of running events. We sincerely look at event piece of feedback both good, bad, and even ugly. We consider each piece for the next event and take it to heart. Each event is an extension of the organization committee and we don’t want to hear negative feedback however we know negative is a part of growth and we incorporate that into the planning for the next event. I can tell you we look at ever single piece of feedback and we adjust based on that. Typically people leave feedback when they had a either negative or positive experience and not typically for mediocre experiences. We absolutely review each piece and adjust accordingly.\nPurpose # We know that these events are free for all to attend and listen. Yes, sponsors get some opt-in information to solicit their services but that is part of their incentive to help us out financially with prizes or covering hosting or other costs. For all of us involved we don’t get paid to organize, we don’t get paid to produce, we don’t get paid as a speaker. This year we had a purpose to host an event that is free for all that want to attend and we also asked that if you found value that you consider making a small donation to the Children’s Hospital of Philadelphia were we had a modest goal of $1,000. Which is not much considering the value of the content that was being delivered over a 2 day time span. We sincerely appreciate all the donations that have come in and at the time of this writing we are so close to reaching that goal. This is a charity that is near and dear to our heart and main organizer, Jason Rivera, which I would like to thank each and everyone that donated. It doesn’t matter if it’s $1,5,10 or more each and every bit counts and it’s a relative indicator that you all felt that this event was worthwhile. We have never asked for money ever for the in person events and we will never will but the response to our small charity drive has been overwhelming that we cannot thank you enough.\n","date":"May 16, 2021","image":"/images/m365-philly-virtual-2021-event-wrap-up/m365-philly-virtual-2021-event-wrap-up-avocado80_a_happy_old_fa-475a88_hu_2ed79b8136a1f001.webp","permalink":"/2021/05/16/m365-philly-virtual-2021-event-wrap-up/","section":"Post","summary":"This past year has been a challenging year for running events. For those that do not know me, I am someone that facilitates many events…","title":"M365 Philly Virtual 2021 Event Wrap Up","type":"posts"},{"content":" Introduction # Trying out the new way to Provision via PnP PowerShell V4 and Azure Functions V3 was a little challenging at first. There are some of the difference and issues that I encountered which I could not get to work correctly. I happen to be one of the early ones that need to do this because Azure had made a change to their interface which prevented using PnP PowerShell V3 and the Microsoft documentation was not updated. Since that time and now Microsoft has updated their online documentation with examples of the exact steps. I’ve decided to post the process I used to get everything working but I still suggest reading the official Microsoft article as well.\nInstall PnP.PowerShell # Open PowerShell 7 (if not installed, please install from here – https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-windows?view=powershell-7.1) Run the following cmdlet to install the PnP.PowerShell module Install-Module -Name \u0026quot;PnP.PowerShell\u0026quot; -RequiredVersion \u0026quot;1.5.0\u0026quot;\nSet up app-only access to your tenant # Open PowerShell 7 and run the following cmdlets to register an Azure App Select the appropriate tenant name before running.\nRegister-PnPAzureADApp -ApplicationName \u0026#34;PnPProvisioningDemo\u0026#34; -Tenant \u0026#34;\u0026lt;tenant-name\u0026gt;.onmicrosoft.com\u0026#34; -DeviceLogin -OutPath . Follow the instructions to complete the Azure AD App registration. You will need to open a browser and enter the url above and supply the code. You will also be asked to log in.\nThis will now take 60 seconds to create the certificate in the out path that was specified in the above command\nAfter the cmdlet is executed. It will create a PFX file. Keep this file saved, as we will have to upload it to Azure function. Login to https://aad.portal.azure.com/ Click -\u0026gt; Azure Active Directory -\u0026gt; App Registration Search for ‘PnPProvisioningDemo’ and the select the App Registration which got created. Copy the Application (client) ID from here. Click API Permission Click API Permissions, and Grant admin consent for {tenant} In the small popup click Yes to approve this app those permissions. Create the Azure function # Go to the Azure Portal. Choose + Create a resource. Type in ‘Function’ and select ‘Function App’ In the Project Details, click on Create New, for the Resource Group Type in ‘PnPProvisioningDemo’ for the resource group name, click OK Enter in a Function App name – this will need to be unique, Runtime Stack, Version and select a Region in which you want the resources inr its created, navigate to your new function App From the dropdown menu, select host.json file and add the following entry and save it. Click ‘Review + create’ Click ‘Create’ After a few moments all of the assets will be created.\nClick ‘Go To Resource’ Next click on ‘App Files’ I typically bump the timeout to 10 mins for the Azure Function from the default 5 minutes. In the App Files \u0026gt; host.json, add this line. Make sure to use a ‘,’ on the previous line \u0026#34;functionTimeout\u0026#34;: \u0026#34;00:10:00\u0026#34; Click Save to lock in the changes Click on the dropdown and select requirements.psd1 You can remove the ‘Az’ = ‘5.*’ as it won’t be used Click Save to lock in the changes Create a new Azure Function Functions \u0026gt; Add: Create a new HTTP Trigger function, by selecting the options as shown below Scroll down, name the function InvokePnPSiteTemplate Set Authorization level, Anonymous Click Save Upload the code for your Azure Function # Go to the Function App main screen and select Advanced Tools Click “Go”, this will open Kudu Select “PowerShell” from the Debug Console menu at the top. Navigate to site\\wwwroot\\InvokePnPSiteTemplate This is where it gets complicated. Hold on to your hats.\nExtract the AzureFunCode.zip file provided as a sample.\nAdd your PnP template into the pnp-template.xml file Update run.ps1 set $filePath to the correct drive [C or D] that you see in the azure portal set $AzAppId to match your Azure App Id set $certFileName to match your exported certificate set $tenant to match your tenant prefix Drag \u0026amp; drop the files to the site\\wwwroot\\InvokePnPSiteTemplate directory NOTE: If you want to use a different version of PnP PowerShell then you can generate the PnP.PowerShell files for upload from the command line. Use the folder that is exported to upload.\nCreate a new folder and then execute the following commands\nSave-Module PnP.PowerShell . After the code is updated. Navigate back to Function screen on Azure portal and click on ‘Get Function Url’ and copy the URL Create the Flow # Go to the Power Automate site, sign in, and choose Create -\u0026gt; Automated Cloud Flow Click Skip at this screen Click ‘untitled’ and give the Flow a name, PnPProvisioningDemo Search for Request, and select Request – When a HTTP Request is received. Enter the following JSON as your request body: { \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: { \u0026#34;webUrl\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34; }, \u0026#34;parameters\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: { \u0026#34;event\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34; }, \u0026#34;product\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34; } } } } } Select + New Step and choose Add an action.\nIn the search box type ‘http’ and click HTTP\nConfigure the properties as shown below. You also need the Azure Function, HTTP endpoint. This is in the Azure Function, Overview screen, click Get Function Url Copy this http endpoint to then paste into the flow Choose Save Flow. This will generate the URL that you will copy in the next step. Back in the Flow, Paste the URI in this box Save the Flow\nOnce save is completed, click back on the first step and copy the Flow HTTP Post Url. Save this for later\nCreate the Site Design # In this section we are going to create a SharePoint Site Design, based on a Team Site template.\nOpen SharePoint Online Management Shell and connect to your tenant using Connect-SPOService Connect-SPOService -Url https://[yourtenant]-admin.sharepoint.com Update the URL property in the code below. Set the url property to the value you copied when you created the flow { \u0026#34;$schema\u0026#34;: \u0026#34;schema.json\u0026#34;, \u0026#34;actions\u0026#34;: [ { \u0026#34;verb\u0026#34;: \u0026#34;triggerFlow\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;{UPDATE THE FLOW URL HERE}\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Apply PnP Template\u0026#34;, \u0026#34;parameters\u0026#34;: {} } ], \u0026#34;bindata\u0026#34;: {}, \u0026#34;version\u0026#34;: 1 } Select the JSON again and copy it, this will put it on your ‘clipboard‘ Open PowerShell and enter the following to copy the script into a variable and create the site script: $script = Get-Clipboard -Raw #before the next step verify you have data in $script by typing this $script #if that is empty try the copy and run the first line again. Add-SPOSiteScript -Title \u0026#34;Contoso Team Config\u0026#34; -Content $script Get-SPOSiteScript You will see a list of one or more site scripts, including the site script you just created. Select the ID of the site script that you created and copy it to the clipboard. Use the following command to create the site design: Add-SPOSiteDesign -Title \u0026#34;Contoso Team\u0026#34; -SiteScripts {Paste the ID of the Site Script here} -WebTemplate \u0026#34;64\u0026#34; Conclusion # That completes all the necessary steps to get the basics up and running. Hope this was helpful walkthrough to make your first time getting started a little easier. The screens are constantly changing in Office 365 but the basics steps should remain the same. Leave a comment below if you get stuck or have a suggestion.\n","date":"Apr 9, 2021","image":"/images/pnp-powershell-v4-provisioning-with-azure-functions-v3-and-power-automate/pnp-powershell-v4-provisioning-with-azure-functions-v3-and-p-d2d4561a_hu_af5eac22ea896aec.webp","permalink":"/2021/04/09/pnp-powershell-v4-provisioning-with-azure-functions-v3-and-power-automate/","section":"Post","summary":"Introduction Trying out the new way to Provision via PnP PowerShell V4 and Azure Functions V3 was a little challenging at first. There are some…","title":"PnP PowerShell V4 Provisioning with Azure Functions V3 and Power Automate / Flow","type":"posts"},{"content":"This blog will cover an example of a MS Teams provisioning process that was originally delivered for M365 California Jan ’21. The sessions were 30 minutes which covered enough time to discuss the entire process. This blog will contain a four videos which dive deeper on the steps during that sessions.\nBuilding a Microsoft Teams Provisioning Process using Power Apps \u0026amp; Power Automate from Thomas Daly\nSetting up the SharePoint List # Setting up the Power Automate Flow # Extending the Flow with MS Graph \u0026amp; Azure Active Directory # Setting up the Power App custom list form and the SharePoint user interaction # This is just an example of one way to provision MS Teams sites and the steps involved in that process, it’s certainly not the only way. Feel free to leave a comment and let me know.\n","date":"Jan 24, 2021","image":"/images/building-a-microsoft-teams-provisioning-process-using-power-apps-power-automate/building-a-microsoft-teams-provisioning-process-using-power--5547434a_hu_9167711255679f1b.webp","permalink":"/2021/01/24/building-a-microsoft-teams-provisioning-process-using-power-apps-power-automate/","section":"Post","summary":"This blog will cover an example of a MS Teams provisioning process that was originally delivered for M365 California Jan ’21. The sessions were 30…","title":"Building a Microsoft Teams Provisioning Process using Power Apps \u0026 Power Automate","type":"posts"},{"content":"I’m writing this blog to share my experiences with working the latest version of PnP PowerShell V4 this .NET Core version and Azure Functions V3.\nBackground # Up until a few weeks ago I was able to write PnP PowerShell provisioning code and run it in an Azure Function V1 to do almost anything I wanted in my SharePoint site. I was receiving warnings that Azure was going to be deprecating the V1 functions but not pay much attention. This forced me to try using the new way of using the Azure V3 function which run PowerShell .NET Core and the new PnP PowerShell libraries built on .NET Core.\nAzure has removed all ways through the UI to use experiment languages. You can set the PowerShell Azure function to run in V1 mode, however the toggle switch to enable experimental languages has been removed. Rendering this article is no longer valid – https://docs.microsoft.com/en-us/sharepoint/dev/declarative-customization/site-design-pnp-provisioning#create-the-azure-function [UPDATE: 11/25/2020 – Microsoft has updated that article]\nThe now older version of PnP PowerShell is referred to as PnP PowerShell V3 which is built on the CSOM for .NET 4.5 Libraries. This runs in PowerShell up V5\nThe newer PnP PowerShell is referred to as PnP PowerShell V4 which is built on the CSOM for .NET Standard 2.0. This run in PowerShell V7.\nYou can read more about this here – https://github.com/pnp/PnP-PowerShell. A tremendous amount of work has been done to make the libraries available for .NET Core. It’s time we all start using the new V4 version and help the project by submitting defects and contributing.\nMy Experience # I was faced with many issues which I will outline. While this was my first time experimenting with the new PnP PowerShell and the Azure Functions V3 it was not an easy one. I wrote this article before changes to official Microsoft documentations updates and had to learn by trial and error which can be tedious.\nOn Demand Loading – the function now use a requirements.ps1 file that contains a manifest of the libraries that your function will using. This is how you load the PnP PowerShell libraries. The files will be downloaded on demand when your function is initialized and will stay in memory until your function goes to sleep. Problems Connecting / Authentication – I believe there is some issues with Authentication as I spent many hours trying various combinations to work. I would recommend using a certificate as that is the only way I was able to get the functions to work correctly. I think it’s possible some pieces of PnP PowerShell V4 are not working 100% in Azure Functions. I was using 0.2.15-nightly Different Commands – not a huge deal but you need to be aware that some of the commandlets have changed and should update them accordingly. It’s well documented just be aware. Pre-Release Version – the PnP PowerShell is still currently pre-release and scheduled to be GA in Jan 2021. We can’t do anything about this, if you need this functionality you will be using the pre-release version. It shouldn’t matter too much if the functionality works with the version of PnP PowerShell that you built it on then you are fine. Somethings may or may not work – always send feedback in the github repo’s for issues. Timeouts – the on demand loading seemed to timeout my functions [default 5 mins max on Azure Function runtime] this was a problem with the libraries taking 3-4 minutes to load and my code only had 1 minute to finish. In the host.json file I had increased the timeout to 10 minutes to account for this. At the time of this article I have not tried to upload and host the libraries in my function [more on this in another article]\nupdating host.json file\nSummary # Since writing this article I have made more progress in this area. I will post a follow up in the next week with a walk through of setting up a site script to kick off a Power Automate Flow, that runs the Azure Function to make some modifications to a SharePoint site. I’ve had a lot of fun playing around and experimenting with different flows and can now appreciate this method along with the power behind it. I am excited to see the progress here and looking forward to future updates from the PnP PowerShell team!\n","date":"Dec 19, 2020","image":"/images/pnp-powershell-v4-and-azure-functions-v3-first-look/pnp-powershell-v4-and-azure-functions-v3-first-look-pnpazurefn-1_hu_56bfda15a3f1d04b.webp","permalink":"/2020/12/19/pnp-powershell-v4-and-azure-functions-v3-first-look/","section":"Post","summary":"I’m writing this blog to share my experiences with working the latest version of PnP PowerShell V4 this .NET Core version and Azure Functions V3….","title":"PnP PowerShell V4 and Azure Functions V3 – First Look","type":"posts"},{"content":"I am frequently asked how do I removed the left navigation on the modern Team sites? Most people don’t know this but if you just remove all the items from the navigation it will go away on it’s own!\nThe draw back is that it’s removed for the whole site. Maybe you just want it gone on one page. Well in that case you’ll have to use CSS to do that. For the one page approach I would add a Modern Script editor to the page.\nImmediately after removing the left navigation, depending on your screen size, you will notice that the screen does not go full width. This would also need to be fixed with injecting CSS either through the page or into the entire site via Application Customizer.\nnot quite full screen\nMicrosoft doesn’t recommend modifying their CSS but they almost give us no choice. For now I’ll leave you with the CSS that works (for now) to update these zones. You’ll need to use one of the two methods above to implement this on Modern pages.\n// Trimming Left Navigation #spLeftNav { display: none; } // Full Screen CSS .CanvasComponent.LCS .CanvasZone { //there is a max width on this set to 1268px, we\u0026#39;ll make it 100% max-width: 100%; } ","date":"Nov 12, 2020","image":"/images/remove-left-navigation-on-modern-team-sites/remove-left-navigation-on-modern-team-sites-full_hu_4e807c44968b120a.webp","permalink":"/2020/11/12/remove-left-navigation-on-modern-team-sites/","section":"Post","summary":"I am frequently asked how do I removed the left navigation on the modern Team sites? Most people don’t know this but if you just…","title":"Remove Left Navigation on Modern Team Sites","type":"posts"},{"content":"Global Microsoft 365 (M365) Developer Bootcamps are happening in major cities all over the world from October 1st through November 30, 2020. These are typically free, one day events held in person at your nearest Microsoft office or even a local companies office. The M365 Developer Bootcamps are designed to be an interactive, deep dive event focusing on developing within the M365 platform. Check out the official website to see if you are having one in your area.\nEach event is run by a local organizer who typically has a small team of other co-organizers. Typically the organizer is someone you might already know that runs your local M365/O365 User Group. They may be a consultant, an employee for a company, self employed, or maybe even Microsoft employee. The point being is that it’s regular people that run these events and not Microsoft. Microsoft plays a huge rule in organizing the support structure behind the event helping with awareness, the global website, supply the facility, subject matter experts, and securing global sponsors for schwag or most importantly lunch. Microsoft is the backbone behind the events while your local community champions obtain speakers, produce schedules, coordinate food/refreshments, generate agenda or content.\nM365 is an ever growing platform of software applications. These events help beginners bridge the gap to entry into building applications using the technologies involved. Each event will have it’s own agenda but would focus on one or many of the follow: Azure, SharePoint, Power Automate, Power Apps, Power BI or MS Teams.\nThis will be our 4th year running our local bootcamp. The first two years we ran the events in New Jersey and New York. This last year I’ve connect with the team out in Pennsylvania and we’ve all joined forces to run the event in NJ, NY and PA for 2019 and 2020. This year’s event will be 100% virtual and open for any one to join us. Initially the intention was to keep it to 30 people hands on but the registration numbers grew to over 700. This is beyond our expectations but we will still deliver the same content in a deep dive demonstration format with the correct subject matter experts for each of the technologies.\nOur NJ, NY, PA bootcamp will be 3 dates each with their own agenda. Register Now\nNov 7, 2020 – MS Teams Governance\nNov 14, 2020 – Power Platform [Power Apps, Power Automate, Power BI]\nNov 21, 2020 – SharePoint Framework [SPFx]\nShout out to our organizer team. Michael Mukalian [Microsoft], Jason Rivera, Manpreet Singh, James Novak.\n","date":"Nov 5, 2020","image":"/images/global-microsoft-365-m365-developer-bootcamp-ny-nj-pa/global-microsoft-365-m365-developer-bootcamp-ny-nj-pa-e16769be_hu_4f1b68b98cd76cee.webp","permalink":"/2020/11/05/global-microsoft-365-m365-developer-bootcamp-ny-nj-pa/","section":"Post","summary":"Global Microsoft 365 (M365) Developer Bootcamps are happening in major cities all over the world from October 1st through November 30, 2020. These are …","title":"Global Microsoft 365 (M365) Developer Bootcamp NY, NJ \u0026 PA","type":"posts"},{"content":" Introduction # Last week I was working on a project that involved setting some managed meta data fields, both the single value and multi values fields. I typically use PnP-JS to streamline the calls to SharePoint however I found there are issues with PNP-Js and managed meta data fields. It’s been said that MMD fields are unable to be set with REST calls (PnP-Js uses REST). There are mixed reviews out there regarding whether it works or not. For me it did not work. My particular scenario involved update 6 single value fields and 1 multi value fields at the same time. While investigating I reach out to a friend Beau Cameron to verify my code as he wrote 2 great articles for single value fields and multi value fields. The results were to flaky using REST so the decision was made to use JSOM. In this article I will share the end code as there were a few things I want to document for future reference.\nUsing JSOM in SPFx # Before you can start writing the code you must first get your project setup in order to work with the JSOM libraries.\nIt’s a good idea to read up on the official guidance as this method follows it but I prefer to load the libraries differently. The Microsoft article loads the libraries in the configuration as external resource but you must specific the full url of your site and I prefer not to lock that value of the site url into a configuration file that would prevent me from using this in another tenant without rebuilding.\nInstall the Typings # install project dependencies\nnpm install @types/microsoft-ajax @types/sharepoint --save-dev In the code editor, open the ./tsconfig.json file, and in the types property, after the webpack-env entry, add references to microsoft-ajax and sharepoint\n{ \u0026#34;compilerOptions\u0026#34;: { // ... \u0026#34;types\u0026#34;: [ \u0026#34;es6-promise\u0026#34;, \u0026#34;es6-collections\u0026#34;, \u0026#34;webpack-env\u0026#34;, \u0026#34;microsoft-ajax\u0026#34;, \u0026#34;sharepoint\u0026#34; ] } } Register the Scripts # In the file you wish to use the JSOM library, require the scripts.\nrequire(\u0026#39;sp-init\u0026#39;); require(\u0026#39;microsoft-ajax\u0026#39;); require(\u0026#39;sp-runtime\u0026#39;); require(\u0026#39;sharepoint\u0026#39;); Loading the JSOM libraries # In the previous section I mentioned that I prefer to dynamically load the libraries to avoid hard coding the site url into the configuration files. In the code below is how I am loading the libraries dynamically.\nNote: In the state I have 2 values\nloadingScripts: boolean, intially set to true Errors: any, initially set to [] This way you can wait until the scripts are loading to rendering fields that require them and I’m also collecting the errors for later consumption.\npublic componentDidMount() { this.loadScripts(); } public getSiteCollectionUrl(): string { let baseUrl = window.location.protocol + \u0026#34;//\u0026#34; + window.location.host; const pathname = window.location.pathname; const siteCollectionDetector = \u0026#34;/sites/\u0026#34;; if (pathname.indexOf(siteCollectionDetector) \u0026gt;= 0) { baseUrl += pathname.substring( 0, pathname.indexOf(\u0026#34;/\u0026#34;, siteCollectionDetector.length) ); } return baseUrl; } private loadScripts() { const siteColUrl = this.getSiteCollectionUrl(); try { SPComponentLoader.loadScript(siteColUrl + \u0026#34;/_layouts/15/init.js\u0026#34;, { globalExportsName: \u0026#34;$_global_init\u0026#34;, }) .then( (): Promise\u0026lt;{}\u0026gt; =\u0026gt; { return SPComponentLoader.loadScript( siteColUrl + \u0026#34;/_layouts/15/MicrosoftAjax.js\u0026#34;, { globalExportsName: \u0026#34;Sys\u0026#34;, } ); } ) .then( (): Promise\u0026lt;{}\u0026gt; =\u0026gt; { return SPComponentLoader.loadScript( siteColUrl + \u0026#34;/_layouts/15/SP.Runtime.js\u0026#34;, { globalExportsName: \u0026#34;SP\u0026#34;, } ); } ) .then( (): Promise\u0026lt;{}\u0026gt; =\u0026gt; { return SPComponentLoader.loadScript( siteColUrl + \u0026#34;/_layouts/15/SP.js\u0026#34;, { globalExportsName: \u0026#34;SP\u0026#34;, } ); } ) .then( (): Promise\u0026lt;{}\u0026gt; =\u0026gt; { return SPComponentLoader.loadScript( siteColUrl + \u0026#34;/_layouts/15/SP.taxonomy.js\u0026#34;, { globalExportsName: \u0026#34;SP\u0026#34;, } ); } ) .then((): void =\u0026gt; { this.setState({ loadingScripts: false }); }) .catch((reason: any) =\u0026gt; { this.setState({ loadingScripts: false, errors: [...this.state.errors, reason], }); }); } catch (error) { this.setState({ loadingScripts: false, errors: [...this.state.errors, error], }); } } ﻿ State values \u0026amp; Taxonomy Controls # To work with the taxonomy fields I will be using the Taxonomy Picker from the PnP React Controls\n\u0026lt;TaxonomyPicker allowMultipleSelections={false} termsetNameOrID=\u0026#34;By Whom\u0026#34; label=\u0026#34;By Whom\u0026#34; values={this.state.byWhom} context={this.props.context} onValueChanged={(pickerTerms) =\u0026gt; { this.setState({ byWhom: pickerTerms }); }} loading={this.state.loading} /\u0026gt; Setting up the JSOM call # This function I I am preparing the JSOM context getting the list and item and will be updating a number of MMD fields. My state values for the MMD are IPickerTerms from PnP React Controls.\nWarning: You can see I have a function called getItem that will retrieve the list item using CAML query. I must do this in order to get a ‘fresh’ version of the item. If you use the JSOM getItemById you will receive a cached copy and performing an update on it will throw an error that it is unable to make a change. I also read that you can avoid that error by calling executeQueryAsync right after getItemById so it’s ‘fresh’ but I went this way.\nprivate updateItemMetaData = async (siteUrl: string, listName: string, itemId: number) =\u0026gt; { console.log(siteUrl, listName, itemId); return new Promise(async (resolve, reject) =\u0026gt; { let context = new SP.ClientContext(siteUrl); let list = context.get_web().get_lists().getByTitle(listName); console.log(\u0026#34;ctx list\u0026#34;, list); let item = await this.getItem(context, list, itemId); console.log(\u0026#34;ctx item\u0026#34;, item); this.setTaxFieldProperty(context, item, list, \u0026#34;ByWhom\u0026#34;, this.state.byWhom, true); this.setTaxFieldProperty(context, item, list, \u0026#34;SubCategory\u0026#34;, this.state.subCategory, true); this.setTaxFieldProperty(context, item, list, \u0026#34;DocumentStatus\u0026#34;, this.state.documentStatus, true); this.setTaxFieldProperty(context, item, list, \u0026#34;SubmittalAction\u0026#34;, this.state.submittalAction, true); this.setTaxFieldProperty(context, item, list, \u0026#34;Phase\u0026#34;, this.state.gaPhase, true); this.setTaxFieldProperty(context, item, list, \u0026#34;Agencies\u0026#34;, this.state.agencies, true); this.setTaxFieldProperty(context, item, list, \u0026#34;Trades\u0026#34;, this.state.trades, false); item.set_item(\u0026#34;ActionDate\u0026#34;, this.state.actionDate); item.update(); context.load(item); context.executeQueryAsync( () =\u0026gt; { console.log(\u0026#34;success\u0026#34;, item.get_id()); resolve(); }, (sender, args) =\u0026gt; { console.log(\u0026#34;fail\u0026#34;, args.get_message()); resolve(); } ); }); }; private getItem = async (context: SP.ClientContext, list: SP.List, itemId: number): Promise\u0026lt;SP.ListItem\u0026gt; =\u0026gt; { return new Promise((resolve, reject) =\u0026gt; { const query = `\u0026lt;View Scope=\u0026#39;Recursive\u0026#39;\u0026gt;\u0026lt;Query\u0026gt;\u0026lt;Where\u0026gt;\u0026lt;Eq\u0026gt;\u0026lt;FieldRef Name=\\\u0026#34;ID\\\u0026#34; /\u0026gt;\u0026lt;Value Type=\\\u0026#34;Integer\\\u0026#34;\u0026gt;${itemId}\u0026lt;/Value\u0026gt;\u0026lt;/Eq\u0026gt;\u0026lt;/Where\u0026gt;\u0026lt;/Query\u0026gt;\u0026lt;/View\u0026gt;`; let camlQuery = new SP.CamlQuery(); camlQuery.set_viewXml(query); let allItems = list.getItems(camlQuery); context.load(allItems, \u0026#39;Include(Id)\u0026#39;); context.executeQueryAsync( () =\u0026gt; { console.log(\u0026#34;success\u0026#34;, allItems); resolve(allItems.get_item(0)); }, (sender, args) =\u0026gt; { console.log(\u0026#34;fail\u0026#34;, args.get_message()); resolve(); } ); }); } ﻿ The Update Function # In this section we will get the field from the list and update it correctly if it’s a single value or a multi value. Each type must be handled differently. For single value we can set the term as an object but for the multi value we must create a string of the terms. Creating the string of terms is done in the getTermString function.\nprivate setTaxFieldProperty = ( context: SP.ClientContext, item: SP.ListItem, list: SP.List, fieldName: string, fieldValues: IPickerTerms, isSingle: boolean ) =\u0026gt; { let field = list.get_fields().getByInternalNameOrTitle(fieldName); let taxField = context.castTo(field, SP.Taxonomy.TaxonomyField) as SP.Taxonomy.TaxonomyField; if (isSingle) { if (fieldValues \u0026amp;\u0026amp; fieldValues.length \u0026gt;= 1) { let termValue = new SP.Taxonomy.TaxonomyFieldValue(); termValue.set_label(fieldValues[0].name); termValue.set_termGuid(new SP.Guid(fieldValues[0].key)); termValue.set_wssId(-1); taxField.setFieldValueByValue(item, termValue); } else { taxField.validateSetValue(item, null); } } else { if(fieldValues \u0026amp;\u0026amp; fieldValues.length \u0026gt;= 1) { let termString = this.getTermString(fieldValues); console.log(termString); let termValueCollection = new SP.Taxonomy.TaxonomyFieldValueCollection(context, termString, taxField); taxField.setFieldValueByValueCollection(item, termValueCollection); } else { taxField.validateSetValue(item, null); } } }; private getTermString(pickerTerms) { let termString = \u0026#34;\u0026#34;; pickerTerms.forEach((term) =\u0026gt; { termString += `-1;#${term[\u0026#34;name\u0026#34;]}|${term[\u0026#34;key\u0026#34;]};#`; }); return termString.slice(0, -2); } Summary # This proof of concept was somewhat challenging to work with the taxonomy fields. So much so that I had to blog it at least for my reference on how I dealt with it. The code above is not meant to be a full solution just a snippet of the key pieces to reliable working with the taxonomy fields. If you are interested in the full look at the code you can check it out here, https://github.com/tom-daly/demos/tree/master/docprocessor. It goes without saying that it’s test/demo code.\n","date":"Oct 29, 2020","image":"/images/using-jsom-to-update-taxonomy-fields-in-spfx/using-jsom-to-update-taxonomy-fields-in-spfx-10.28.2020-21.10_hu_5c010e682a1697fc.webp","permalink":"/2020/10/29/using-jsom-to-update-taxonomy-fields-in-spfx/","section":"Post","summary":"Introduction Last week I was working on a project that involved setting some managed meta data fields, both the single value and multi values fields….","title":"Using JSOM to update Taxonomy Fields in SPFx","type":"posts"},{"content":" Introduction # Over the past few months I’ve been involved in many discussions about SharePoint Hub Sites. What I’ve found is that majority of the people involved in the discussions were confused about what SharePoint Hub Sites are, what they do and what they do not do. In this article I will explain a little more about Hub Sites, what you get and call out specifically what you will not get.\nSite Architecture # Before we begin, it is important to understand the structure of sites in SharePoint. The terminology can be somewhat confusing as it can mean something different given a different context. Since this article is all about modern sites I will use site and site collection to mean the same thing.\nAll modern sites are site collections, in SharePoint. This means that each has its own set of features, security, theme, site columns, content types, etc.. By hubbing those sites, they still remain their own entity but with connections between. The following sections will call out the pro’s \u0026amp; cons along with some of the common misconceptions.\nSearch # By default: Searching has two levels of scoping, within the current site and within the tenant. Tenant level searches everything you have access to.\nAfter hubbing: Searching now has three levels of scoping, within the current site, within the hub, within the tenant. Searching within the hub level will find documents in any of the hubbed sites that you have access to.\nNavigation # By default: Each site has it’s own navigation. Communication site have a top navigation and Team sites have a left navigation.\nAfter hubbing: A new navigation is added above the header but below the suite bar. This is the hub site navigation. All sites that are hubbed will receive this navigation. This creates a consistent top navigation across all sites within the hub.\n![SharePoint\nHome Marketing Marketing Home Documents v p Sear Site contents Edit ](../../images/hub-site-guide/hub-site-guide-image-1.png) The new navigation is just a collection of link that you must adjust manually. Newly created sites will not be auto added to the hub navigation and the navigation is not security trimmed, however it can be audience targeted [Releasing April 2020].\nCommon Misunderstandings:\nThe hub navigation displays all the sites in the hub – No the navigation is a simple list of links that you manually have to maintain. The hub navigation is security trimmed – No the navigation is not security trimmed, however sites are still security trimmed so you can see the links but you would be blocked from the site if you do not have access. Security # By default: Each site has it’s own security and permissions. As an admin you need to grant access for users and groups to each site individually. It could be possible to have access to the hub and not a hubbed site.\nAfter hubbing: Nothing changes. Security still must be granted to each site individually.\nCommon Misunderstandings:\nPermissions from the hub site rolls down into the hubbed sites – No each site within the hub has it’s own security. Permissions applied to the hub do not apply to the hubbed sites. Branding # By default: Each site can has their own branding. To be crystal clear, in modern sites, branding equals:\nTheme – General colors Header – Layout [Standard or Compact] and Background Navigation – Menu Style [Mega menu or Cascading] Hub Navigation Logo – the logo located in the hub navigation After hubbing: Each site within the hub will receive the all of the items from the above list.\nCommon Misunderstandings:\nAll branding customization will be applied to sites within the hub – No only theme colors will applied to sites within the hub. Branding customizations can be applied via features and no features are automatically applied by hubbing a site. This means that any application customizer that you built to adjust the branding further or inject functionality will not be automatically roll down into a hub site. Each hub site maintains its own collection of features and you must activate/deactivate these individually Hubbing does not inherit the footer or site logo – I can’t believe it doesn’t do this hopefully it will in the future. Content Rollup # By default: Sites do not share information with each other directly. You could use a search based web part to find and display a particular type of content across all sites within your tenant.\nAfter hubbing: The Highlight Content web part can be used to aggregate various content from within the hub. News Articles and Events can also be aggregated up to the hub site.\nCommon Misunderstandings:\nYou still must have access to this content within the hubbed site it came from, otherwise you wouldn’t see it. General # Below are a list of a few other considerations that important to know:\nHub site do not share site columns or content types You cannot hub a site to another hub site Summary\nI think hub sites bring some much needed functionality to logically organizing a bunch of disparate sites together to form an intranet. In my opinion this is where modern SharePoint sites are failing. We need to be able to organize them together to be able to travel between and across them as well as locate information in each. This is exactly what hub sites solve. I hope this article shed some light on some of the edges. Hub wisely my friends!\n","date":"Oct 29, 2020","image":"/images/hub-site-guide/hub-site-guide-pexels-photo-1595485_hu_7ebe47e6ba1f1c4b.webp","permalink":"/2020/10/29/hub-site-guide/","section":"Post","summary":"Introduction Over the past few months I’ve been involved in many discussions about SharePoint Hub Sites. What I’ve found is that majority of the people…","title":"Hub Site Guide","type":"posts"},{"content":"I am humbled and honored 🙂 to be renewed as a Microsoft MVP for 2020-2021. Anyone that received this award should be humbled and honored. It’s a tremendous task to be considered for this award.\nSo what makes me an MVP? I have no problem in telling you that regardless of the title I live and breathe on help people around me learn the new technologies coming out in the Office 365 stack. My focus is on SharePoint development but it doesn’t stop there.\nI am a huge proponent of live events and enriching communities. In the past year, I’ve traveled to numerous states to deliver talks about SharePoint and Office 365 development. I’ve demonstrated GitHub projects showing what you can and shouldn’t do working within SharePoint modern. I’ve worked with others to deliver live events on Office 365 \u0026amp; Azure in NJ, NYC, and PA.\nI can say in the past year I’ve given those talks to as little as 5 people to groups pushing 100. I get more satisfaction speaking to 5 than 100 but that’s another discussion. The point is it really doesn’t matter on how many people attend your sessions, subscribe to your blogs/youtube, follow you on social media. I do it because that’s what I enjoy doing and if it helps one person I am satisfied that my mission is accomplished. The reality is that your experience and story doesn’t only touch one person, it effects many. We are all learning together – constantly, as am I.\nI have spent at least 5 years doing what I love to prior to becoming recognized for this award and I wouldn’t change a thing. I never started out saying that I must become a Microsoft MVP. The title came much later when my actions and involvement spoke for themselves.\n2020 will be a challenge. How can I run events in my community given we can’t meet? I am not a fan of online events however, I am doing my best to deliver live online content through SharePoint Saturdays and other outlets. I am working with other organizers in the community to help events plan, gather resources and execute their online events. For the next few months I am focusing on delivering more online content while we wait for the time when we can meet in person.\n","date":"Oct 14, 2020","image":"","permalink":"/2020/10/14/renewed-as-microsoft-mvp-office-develop-2020-2012/","section":"Post","summary":"I am humbled and honored 🙂 to be renewed as a Microsoft MVP for 2020-2021. Anyone that received this award should be humbled and honored….","title":"Renewed as Microsoft MVP – Office Server \u0026 Services 2020-2021!","type":"posts"},{"content":"I recently built a handful of amazing content entry web parts for Office 365 using SharePoint Framework (SPFx). The client I was working with wanted various combinations of responsive columns, rich text editor, links, and pictures. This ultimately led to building out a few custom web parts to meet their needs.\nAbout a months later, as we started focusing on search, I noticed a huge problem. The contents of my web parts were not searchable! We couldn’t find anything. This was the first time I had to ensure the content was indexed on a modern page. It’s not the same as classic – lesson learned.\nStep 1 – Mark Your SPFx Web Part Properties # I came across the official Microsoft guidance which tells you to mark your properties. I will not go into details of the article, you can read the full article here.\nAs you can clearly see you need to mark the web parts as image, html, string, or link. If you have a combination of those then use html.\nMicrosoft’s Example\nYet another issue. What about web parts that have arrays, objects, arrays of objects or props that leverage lists as a data source?\nHow to handle an arrays / objects / both … simplify # Here is an example from one web part. I have a web part property which is an Array of Objects, that have multiple properties. According to the guidance, how do you mark it?\nThere is no immediate way to tag this properties where it would get indexed correctly and make the content searchable. So how do you solve this? The list below are the main points.\nCreate a new property ‘searchableText’ Mark the new property ‘isHtmlString’ Create an update function Step 1 and 2 are very straightforward. These are used inside the main .ts file of the web part. It will not be passed into the child components.\nCreate New Property\nMark New Property\nThe 3rd step is to pass a function to child component. When the items are changed inside the web part, it will loop over the items and create an html string of the text \u0026amp; links to update searchableText. Once that is complete we will be able to search on the content of the web part properly.\nThe purpose of this example is give you an idea of converting your objects / arrays into a String. It’s a little extra work but making it worthwhile when users can search the contents of your web parts. I have yet to figure out a way to make list based web part content searchable on the page. [I do not want to hit the list items directly in many cases] That might be a blog for another time.\n","date":"Jun 19, 2020","image":"/images/making-spfx-web-parts-searchable-lessons-learned/making-spfx-web-parts-searchable-lessons-learned-m1-4_hu_87d7f051708ea43.webp","permalink":"/2020/06/19/making-spfx-web-parts-searchable-lessons-learned/","section":"Post","summary":"I recently built a handful of amazing content entry web parts for Office 365 using SharePoint Framework (SPFx). The client I was working with wanted…","title":"Making SPFx Web Parts Searchable – Lessons Learned","type":"posts"},{"content":"I started this project over a year and a half ago and at the time Modern SharePoint starts gaining some traction with organizations. I remember SPFx v1.4 was being released at the time and how far we’ve come from that today. I still find this project very interesting and useful today. It’s something different and on communication, sites can give you an option for navigation. Without further delay, I present to you the Side Navigation Project [inspired by the Office 365 Admin Tenant Navigation].\nCurrently Supported [O365/SP2019 – IE11, Chrome, FF]\nThe Side Navigation Project with full documentation can be found in my github repo https://github.com/tom-daly/spfx-side-navigation . That’s where you will find the installation guide and documentation. Please post any issues there – I’ll do my best to help if you are having trouble. This blog’s purpose to give a bit of background on the project and help showcase it.\nAbout the Project # The Side Navigation purpose is to add a left navigation on Modern Communication site template that is unobtrusive to the main UI. It’s normally slim and compact but can be easily expanded to see what the icons nodes are.\nWhat makes it very cool is the fact that you can incorporate ANY svg icon. The only requirement is that it’s a single color icon – we use the SVG code in the list item and this allows us to recolor it along with the navigation nodes!\nInstallation # On installation and activation within your site it will create a list called Side Nav List. This list contains all the information needed to sustain the navigation. You can even create sub nodes b/c the list contains a lookup to itself. [this could be extended to infinite levels however the css does not support that currently / the code does]\nThe Side Navigation is an SPFx application customizer that lives in the header area (if you are familiar with the placeholders) but it styles itself to the left of the page and pushed the main content over 50px. It fits nicely under the waffle or app launcher.\nCustomization # Colors # You might be looking at it and say those colors aren’t going to fit site theme. We’ll you can download the solutions and QUICKLY change the colors of the site-menu.css file.\nIcons # You can use any monochrome SVG you want as long as you can access the source [just open it in notepad]. SVG’s are freely available for a few dollars. My favorite sites for SVGs\nhttps://www.flaticon.com/ https://www.iconfinder.com/ https://icons8.com/ https://freeicons.io/ Navigation (advanced) # Lastly the data source. Currently a list is deployed to the site on installation of the app. However you can make this global if desired. You can check out my global navigation project. You can modify the navigation provider to point to another source and when you deploy this out to various sites it will connect back to that source list.\nCheck out the global navigation project and the GlobalNavProvider code – here … and you can see how i feel that provider a url. The current project also has a SideNavProvider which is set to look for the list in the current site but with a little develop know how you can change that !\nWhy Navigation? # If you haven’t seen my github repo’s before I have specialized in UI for all versions of SharePoint and I have many navigation projects going back to 2010/2013. If you like this project you’ll also like the Global Navigation Solution. It’s literally the same code! 😉 just styled a bit differently. The global navigation has some other enhancements as people have helped by submitting issues and requests. You can check that out here https://github.com/tom-daly/spfx-global-navigation\n","date":"Jun 9, 2020","image":"/images/spfx-side-navigation-project-for-modern-sites/spfx-side-navigation-project-for-modern-sites-title-2_hu_ac1d121ed4a6c5b2.webp","permalink":"/2020/06/09/spfx-side-navigation-project-for-modern-sites/","section":"Post","summary":"I started this project over a year and a half ago and at the time Modern SharePoint starts gaining some traction with organizations. I remember…","title":"SPFx Side Navigation Project for Modern Sites","type":"posts"},{"content":"Recently I had to provision a number of modern pages w/ some additional page properties. These page properties were custom fields added as columns to the Site Pages library. I found it somewhat annoying that there was not direct way to provision a page via PnP PowerShell with properties.\nThis blog covers the 2 steps w/ code on creating a new modern page (aka client side page) and then getting that page as a list item and updating its properties.\nStep 1 of creating the page is very simple but, as you can see, you cannot supply additional values for custom properties. https://docs.microsoft.com/en-us/powershell/module/sharepoint-pnp/add-pnpclientsidepage?view=sharepoint-ps\nAdd-PnPClientSidePage -Name \u0026#34;My New Page\u0026#34; -PromoteAs NewsArticle -Publish Step 2 is to find the page and then update it as a list item. In case you didn’t know, everything inside the -Values @{} are your custom properties. https://docs.microsoft.com/en-us/powershell/module/sharepoint-pnp/set-pnplistitem?view=sharepoint-ps\n$page = Get-PnPListItem -List \u0026#34;Site Pages\u0026#34; -Query \u0026#34;\u0026lt;View\u0026gt;\u0026lt;Query\u0026gt;\u0026lt;Where\u0026gt;\u0026lt;Eq\u0026gt;\u0026lt;FieldRef Name=\u0026#39;Title\u0026#39;/\u0026gt;\u0026lt;Value Type=\u0026#39;Text\u0026#39;\u0026gt;My New Page\u0026lt;/Value\u0026gt;\u0026lt;/Eq\u0026gt;\u0026lt;/Where\u0026gt;\u0026lt;RowLimit\u0026gt;1000\u0026lt;/RowLimit\u0026gt;\u0026lt;/Query\u0026gt;\u0026lt;/View\u0026gt;\u0026#34; Set-PnPListItem -List \u0026#34;Site Pages\u0026#34; -Identity $page.ID -Values @{\u0026#34;Title\u0026#34;=\u0026#34;My New Page\u0026#34;;\u0026#34;Classification\u0026#34;=\u0026#34;News\u0026#34;;\u0026#34;Article_x0020_Date\u0026#34;=$startDateTime.AddDays($i); \u0026#34;LocationTag\u0026#34;=\u0026#34;Enterprise Taxonomy|Locations|$location\u0026#34;;} I was unable to find a command-let to just get me the page by title. This CAML query should help you out.\n","date":"Jun 1, 2020","image":"","permalink":"/2020/06/01/set-modern-page-properties-with-pnp-powershell/","section":"Post","summary":"Recently I had to provision a number of modern pages w/ some additional page properties. These page properties were custom fields added as columns to…","title":"Set Modern Page Properties with PnP PowerShell","type":"posts"},{"content":" Introduction # Over a year ago I create a cross site collection navigation solution aka global navigation for Modern SharePoint sites using all the Microsoft goodies like SPFx, PowerShell PnP, PnP-JS \u0026amp; Office UI Fabric. At the same time, I had a very similar solution for Classic SharePoint sites for a different batch of clients. It was fairly typical for clients new to Office 365 go straight Modern and some of the more traditional SharePoint clients to stick with Classic sites.\nOne day I was asked to put the global navigation on the Classic site and the Modern sites. It just didn’t feel right to me having 2 separate solutions. One being built with jQuery and JavaScript while the other built in SPFx using TypeScript, SCSS \u0026amp; React. The SPFx being the more current way of developing client-side solutions has spoiled me. I don’t dislike jQuery / JavaScript solutions… I just prefer the tooling SPFx offers for us SharePoint developers.\nHow can an SPFx Application Customizer work on Classic sites? # If you break it down, part of the secret sauce behind SPFx is that it’s simple a wrapper around plain old JavaScript that tells SharePoint where your file is and load it. SPFx provides other greater capabilities, like property pane which passes parameters to your JavaScript or React components.\nFor Modern sites when you create a web part or app customizer there is a top level file. For example: in the global navigation project – SpfxGlobalNavigationApplicationCustomizer.ts. This file registers the Application Customizer and then loads the GlobalNav component into the Header placeholder [available on all Modern pages]. Everything inside the GlobalNav.tsx is React and nothing special.\nFor Classic sites there is no built-in framework to inject components. This is something that must be addressed. In the global navigation project I create a component called ClassicMode.ts which is the top level file.\nInside that file there is a reference to include @bable/polyfill. That helps supplement any browser shortcomings. On a Modern site there are already some polyfills so this would be unnecessary.\nNext there is a secondary style sheet which would be used to handle style differences between Classic \u0026amp; Modern sites. In the global navigation project, I am reference the Office UI Fabric library for icons. On a Modern site those are also already there so this would be unnecessary.\nFinally, on window load, the GlobalNav component is created into the element #DeltaTopNavigation. The DeltaTopNavgiation is the element where the default navigation is on a Classic SharePoint site.\n** For this navigation scenario I am overwriting the default out of the box navigation. You can create a new DOM element and inject that anywhere and mount your React component into that new element. **\nThat’s it in terms of the code. The code of the GlobalNav component is the same for both Modern \u0026amp; Classic sites. The Modern Application Customizer points to the same files that the ClassicMode component points to.\nBuilding an SPFx Application Customizer for Classic Sites # Going back to what I said earlier, SPFx is simply a wrapper around plain old JavaScript. SPFx allows you to write in TypeScript/React/SCSS but browsers only understand HTML/CSS/JS, not those other languages. All of the SPFx code gets compiled into a single JavaScript file.\nYou can’t just extract the JavaScript file from the .sppkg package and pop it into the Classic site.. that’s not going to work. The next step is to generate a new JavaScript that uses our new ClassicMode.ts file as an entry point. The key to this is Webpack.\nIn the SPFx Application Customizer folder create a webpack.config.js. This will tell webpack to use our ClassicModer.ts as the entry point and produce a new file called top-navigation.js in a folder called classic-dist\nTo execute webpack against this new file just run the following command:\nnpx webpack --config webpack.config.js\nNote: This webpack file uses a few new dependencies that are NOT included with an SPFx project. You must include these new dependencies before you attempt to run webpack against this new config file.\nAttaching Classic Mode build to SharePoint Site # The last thing we need to cover is how to hook up this file into your Classic SharePoint site. It’s straight forward and if you made it this far it should be a piece of cake.\nStep 1 – Upload the file. You can manually copy / paste the file into SharePoint. Alternatively you could host this file on a CDN if the plan is to use it on more than 1 site collection.\nStep 2 – Connect the JavaScript file to your site collection. You can’t do this through the UI unless you use a tool like SP-Editor for Chrome to attach a site script link\nAlternatively, check out how I upload it and activate it using PowerShell PnP with a deploy script.\nClassic Mode Deploy Script\nConclusion # That wraps up how to use your SPFx Application Customizer on a Classic SharePoint site. The most complicated part for me was building out the webpack.config.js file. It was a little tricky finding the right polyfills, getting the loading to work and testing across versions of SharePoint. I have even used this approach, for this project SPFx Global Navigation Application Customizer, on a SharePoint 2013 site. Interested in that solution check out my other article Cross Site Collection Navigation for Modern \u0026amp; Classic SharePoint Sites. Enjoy!\n","date":"Aug 28, 2019","image":"/images/spfx-application-customizer-that-works-on-classic-sharepoint-sites/spfx-application-customizer-that-works-on-classic-sharepoint-c593bae1_hu_ecb69ccda3623c2f.webp","permalink":"/2019/08/28/spfx-application-customizer-that-works-on-classic-sharepoint-sites/","section":"Post","summary":"Introduction Over a year ago I create a cross site collection navigation solution aka global navigation for Modern SharePoint sites using all the Microsoft …","title":"SPFx Application Customizer that works on Classic SharePoint Sites","type":"posts"},{"content":"TL;DR Go here for the code + instructions: https://github.com/tom-daly/spfx-global-navigation\nModern version works with: Office 365 \u0026amp; SharePoint 2019 – IE 11, Chrome, Firefox.\nClassic version works on any SharePoint [yes any] – IE 11, Chrome, Firefox\nIntroduction # Maintaining a consistent navigation across all site collections has always been a problem for SharePoint. Site collections do not share navigation. The story goes back to the beginning of SharePoint’s existence. In earlier versions 2007, 2010… it was recommended to break up sites into site collection to keep the content database sizes small. Microsoft had recommended maximums for best performance. Ultimately the content of SharePoint are table in a SQL databases and small databases meant faster, more manageable backups. Alternatively you could have all your sites in one site collection and then all the out of the box navigation options would be perfect.\nEnter Modern… Each modern site is a site collection. This approach creates a flat hierarchy of sites. Each site collection having its own navigation, security, look and feel, content, components and more.\nHow can you create a consistent navigation across modern pages? Microsoft’s answer is to use Hub sites. “Hubbing” your sites will add a hub site navigation element to the top of the page.\nGreat! Hub sites to the rescue… What if you have some classic sites? Most organizations are currently in a hybrid mode of classic and modern pages. You can’t hub classic sites.\nThis blog will cover the solution that I built to add a list based navigation to modern pages using SPFx application customizer. But wait there’s more… It will also show you how you can use the same code base and repackage it to work on Classic SharePoint sites as well. Essentially creating the perfect cross site collection navigation solution that works on both classic and modern.\nSolution Overview # The solution is broken down into a couple of parts. This blog is meant to explain the approach and the github repository contains an in depth guide to install and provision.\nNavigation List – PnP Powershell Template + Provisioning scripts SPFx application customizer – Global Navigation – React Component Step 1 – Provision the Global Navigation List\nI’m all about repeatable provisioning – as a developer I almost never create anything manually. In this solution I use Powershell PnP, mainly the provisioning template to provision the site columns, content types and list schema and connecting a lookup like with a cmdlet.\nUsing the list based approach, I can centrally host the list in the root site of the tenant where my users generally have read access. In the case that you have secured the root site from users, you could break permissions on the Global Nav List and grant read only access.\nThe list based approach has benefits that I like:\nIt’s easy to talk to the list and using a simple rest query or PnP-JS as I do in this solution. Lists can be security trimmed so you can hide nodes from certain groups by breaking inheritance on those items. For those items they would have item level permissions. Lists are easy to work with for the end users, they don’t need lengthy instructions on how to manage the navigation The entire list is easily deployable via the deploy.ps1 in the provisioning folder of the project. Check that out for an example of a template and the standard script I use to provision assets via Powershell PnP\nStep 2 – SPFx Application Customizer # As of now the only way to install the solution is to build it. Clone or fork the repo and you’ll need to have a SPFx development environment in order to complete the build. Check out the github for the full instructions. Once you have the code, the build and deployment is the standard process for SPFx apps.\nFuture versions I might include a package that you download, let me know if that’s something you might want. Most people will want to at least customize the colors and best way is to get the code and do it yourself.\nThe SPFx Application Customizer lives inside the Header placeholder on any modern site that you add the app to. The code has been written such that it makes a call to the Global Nav List on the “/” root site of the tenant. You could change that location in the code and rebuild if you desired by modifying the GlobalNavProvider.tsx\nGlobal Navigation\nOnce you’ve installed the list, add some nodes, deploy the app customizer, activate the app on a site you should get something like this below. The navigation can support as many levels as you want.\nClassic Build # Yes it works on Classic SharePoint as well. By far the coolest part of this whole project. How can that be? For the complete explanation check out this article: SPFx Application Customizer that works on Classic SharePoint Sites.\nClassic site deployment process is different. It’s not an app / app package. The solution creates a separate JavaScript file that is uploaded to your site and linked via a site script link.\nTo generate the latest JavaScript run the ‘build.cmd‘. That will generate both a new .sppkg for Modern sites and the .js file for Classic sites. After a successful build, the top-navigation.js file will be in the ‘classic-dist‘ folder.\nIf you want an automated deployment approach you would also want to run the deploy.ps1 file. The deploy script will upload the file and attach the JavaScript to your site collection. If deploying to multiple sites it’s a good ideal to centrally host this file in a CDN.\nOnce the file is attached via a script link, it’s live. Your site should look like this.\nConclusion # There you have it, a global navigation solution built for Modern sites using SPFx, Powershell PnP, PnP-JS, and Office UI Fabric that also works on Classic sites. One code base to maintain for both versions of SharePoint. This provides a flexible, consistent navigation for customers that have a mix of Classic \u0026amp; Modern sites.\n","date":"Aug 28, 2019","image":"/images/cross-site-collection-navigation-for-modern-classic-sharepoint-sites/cross-site-collection-navigation-for-modern-classic-sharepoi-563b72c9_hu_5f9d18d0676eb285.webp","permalink":"/2019/08/28/cross-site-collection-navigation-for-modern-classic-sharepoint-sites/","section":"Post","summary":"TL;DR Go here for the code + instructions: https://github.com/tom-daly/spfx-global-navigation Modern version works with: Office 365 \u0026amp; SharePoint 2019 – IE 11, …","title":"Cross Site Collection Navigation for Modern \u0026 Classic SharePoint Sites","type":"posts"},{"content":"My goal is to write clean and readable code. The least I can do is make sure it’s formatted correctly. One of my favorite Visual Studio plugins is called Prettier. I use that plugin to make sure everything is formatted consistently throughout my code. My biggest issue is that it adds semicolons to every single function.\nBut it doesn’t treat all functions equal.\nThe SPFx tslint rules don’t like that.\nPrettier has the option to enable / disable semicolons at the end of every statement. Disabling that setting takes away all the semicolons from all lines. I like to have semicolons after each statement.\nYou could disable the semi-colon check in SPFx project’s tslint.json file\nOR you could edit your gulpfile.js and add suppression to prevent the message.\nThe build.AddSuppression will accept a string or a regex. You can see here I’ve added a regex value to suppress errors that end with ‘error semicolon: Unecessary semicolon”\nEither way works for me – no more semicolon warnings!\n","date":"Jul 15, 2019","image":"","permalink":"/2019/07/15/suppress-spfx-warnings-w-regex/","section":"Post","summary":"My goal is to write clean and readable code. The least I can do is make sure it’s formatted correctly. One of my favorite Visual…","title":"Suppress SPFx warnings w/ Regex","type":"posts"},{"content":"I’m thrilled to announce that I’ve been re-awarded my MVP for the upcoming year. This past year had some unique challenges with the birth of my 2nd little girl. She didn’t like to sleep and required much more attention that our first child. Let’s just say my time became a precious commodity. I magically managed to execute a number of local events for my NJ and NYC community, travel around the country to a number of fantastic SPS Events [San Francisco, Boston, DC, Philly] and to top it all off, squeezed in a trip to India for SPS Ahmedabad and to visit one of the SoHo Dragon offices to work with a talented group of people.\nI look forward to the road ahead – I already have planned a number of events where I’ll be speaking . I also have a number of local events I plan to run. I’m hoping to bring back an SharePoint Saturday NJ in the form of O365 or maybe M365 Saturday? I also plan to increase some of my online contributions… I have so many useful tips and example projects that I would love to share if I can just find more of that precious time.\n","date":"Jul 3, 2019","image":"","permalink":"/2019/07/03/renewed-office-server-services-microsoft-mvp-for-2019-2020/","section":"Post","summary":"I’m thrilled to announce that I’ve been re-awarded my MVP for the upcoming year. This past year had some unique challenges with the birth of…","title":"Renewed Office Server \u0026 Services Microsoft MVP for 2019 – 2020","type":"posts"},{"content":"I’ve been using the auto generated build numbers paired with Stefan Bauer: How to version new SharePoint Framework projects and it’s been working great for myself. There is however, one minor issue. After running ‘npm version major/minor/patch’ it will also run the build/bundle that automatically increments the revision numbers. This is an unwanted side effect.\nRunning ‘npm version major/minor/patch’ requires a clean tree, but the build/bundle modifies the package-solution.json file causing a problem. I’ve updated the build/bundle code to accept a parameter that will NOT automatically increment the revision number. Ultimately I’m fixing a problem that I caused.\nLet’s take a see an example.\nFirst thing that you need is an extra npm package called ‘gulp-util’\nnpm install --save-dev gulp-util Here are the gulpfile.js enhancements again, specifically gutil \u0026amp; fs\nconst gulp = require(\u0026#39;gulp\u0026#39;); const build = require(\u0026#39;@microsoft/sp-build-web\u0026#39;); const gutil = require(\u0026#39;gulp-util\u0026#39;); const fs = require(\u0026#39;fs\u0026#39;); Next add the gulp task to bump the revision. You can see that this task is looking for an argument called ‘–no-revision’. If ‘–no-revision’ is specified in the build or bundle command it will noop on the bumpRevision task.\nvar getJson = function (file) { return JSON.parse(fs.readFileSync(file, \u0026#39;utf8\u0026#39;)); }; let bumpRevisionSubTask = build.subTask(\u0026#39;bump-revision-subtask\u0026#39;, function(gulp, buildOptions, done) { var skipBumpRevision = buildOptions.args[\u0026#34;revision\u0026#34;] === false; if(!skipBumpRevision) { var pkgSolution = getJson(\u0026#39;./config/package-solution.json\u0026#39;); var oldVersionNumber = String(pkgSolution.solution.version); gutil.log(\u0026#39;Old Version: \u0026#39; + oldVersionNumber); var oldBuildNumber = parseInt(oldVersionNumber.split(\u0026#39;.\u0026#39;)[3]); gutil.log(\u0026#39;Old Build Number: \u0026#39; + oldBuildNumber); var newBuildNumber = oldBuildNumber+1; gutil.log(\u0026#39;New Build Number: \u0026#39; + newBuildNumber); var newVersionNumber = oldVersionNumber.substring(0, String(oldVersionNumber).length - String(oldBuildNumber).length) + String(newBuildNumber); gutil.log(\u0026#39;New Version: \u0026#39; + newVersionNumber); pkgSolution.solution.version = newVersionNumber; fs.writeFile(\u0026#39;./config/package-solution.json\u0026#39;, JSON.stringify(pkgSolution, null, 4)); } return gulp.src(\u0026#39;./config/package-solution.json\u0026#39;) .pipe(skipBumpRevision ? gutil.noop() : gulp.dest(\u0026#39;./config\u0026#39;)) }); let bumpRevisionTask = build.task(\u0026#39;bump-revision\u0026#39;, bumpRevisionSubTask); Lastly register the bumpRevisionTask with the build process.\nbuild.rig.addPreBuildTask(bumpRevisionTask); Again … all this is above the following\nbuild.initialize(gulp); Now in order to skip the bumpRevisionTask you can call build/bundle like this:\ngulp build --ship --no-revision gulp bundle --ship --no-revision That’s it… add this and you will get auto incrementing revision number w/ the option to NOT increment the build number.\nWhy is that so great? Because, if you pair this with Stefan Bauer’s npm versioning post it will run when you execute the ‘npm version major/minor/patch’ command causing a version like v1.0.3.1 … instead of v1.0.3.0\nI’ll leave you with an example of my build commands for new versions that I’m releasing to dev/staging/prod.\nbuild-patch.cmd\ncls call gulp clean call gulp build --ship --no-revision call gulp bundle --ship --no-revision call npm version patch call gulp package-solution --ship call explorer .\\sharepoint\\solution\\ build-minor.cmd\ncls call gulp clean call gulp build --ship --no-revision call gulp bundle --ship --no-revision call npm version minor call gulp package-solution --ship call explorer .\\sharepoint\\solution\\ build-major.cmd\ncls call gulp clean call gulp build --ship --no-revision call gulp bundle --ship --no-revision call npm version minor call gulp package-solution --ship call explorer .\\sharepoint\\solution\\ I recommend reading some of my other posts on this subject\nOriginal Post: SPFx Automatically Generating Revision Numbers\nSimple Build Script for the SharePoint Framework\nStefan Bauer: [How to version new SharePoint Framework projects](How to version new SharePoint Framework projects)\nEnjoy! let me know how this works for you and your development teams.\n","date":"Aug 21, 2018","image":"/images/update-spfx-automatically-generating-revision-numbers-versioning/update-spfx-automatically-generating-revision-numbers-versio-418bcacd_hu_2854fa75527c0673.webp","permalink":"/2018/08/21/update-spfx-automatically-generating-revision-numbers-versioning/","section":"Post","summary":"I’ve been using the auto generated build numbers paired with Stefan Bauer: How to version new SharePoint Framework projects and it’s been working great for …","title":"Update: SPFx Automatically Generating Revision Numbers + Versioning","type":"posts"},{"content":"I like automatic revision numbers while I’m developing. I don’t have to manually be making changes while pushing packages out and testing. While working on a team I can see that others have made changes based on the build number alone. By seeing the number go up I am more confident that their change was deployed and I am testing a new release of their deployed code. In general I believe that is often overlooked for developers to manually update build numbers before deploying their test code or production code for that matter.\nSkip to the bottom there is a nice write up from Stefan Bauer on ‘How to version new SharePoint Framework projects’. I like how he use npm for it’s versioning and then he ties it into SharePoint project versioning.\nThis article can be used in conjunction to his versioning or by itself and you can continue to manually increment your major/minor/patch versions.\nLet’s Begin # You will make these changes in your projects gulpfile.js. The last line inside your gulpfile.js should be:\nbuild.initialize(gulp); Modifications to the gulpfile.js # At the top require fs for file reading \u0026amp; writing\nconst fs = require(\u0026#39;fs\u0026#39;); Add the function that will read json files.\nvar getJson = function (file) { return JSON.parse(fs.readFileSync(file, \u0026#39;utf8\u0026#39;)); }; Add the function which will perform the version update and define a task called ‘bump-build-version’\nlet bumpRevisionSubTask = build.subTask(\u0026#39;bump-revision-subtask\u0026#39;, function(gulp, buildOptions, done) { var pkgSolution = getJson(\u0026#39;./config/package-solution.json\u0026#39;); var oldVersionNumber = String(pkgSolution.solution.version); gutil.log(\u0026#39;Old Version: \u0026#39; + oldVersionNumber); var oldBuildNumber = parseInt(oldVersionNumber.split(\u0026#39;.\u0026#39;)[3]); gutil.log(\u0026#39;Old Build Number: \u0026#39; + oldBuildNumber); var newBuildNumber = oldBuildNumber+1; gutil.log(\u0026#39;New Build Number: \u0026#39; + newBuildNumber); var newVersionNumber = oldVersionNumber.substring(0, String(oldVersionNumber).length - String(oldBuildNumber).length) + String(newBuildNumber); gutil.log(\u0026#39;New Version: \u0026#39; + newVersionNumber); pkgSolution.solution.version = newVersionNumber; fs.writeFile(\u0026#39;./config/package-solution.json\u0026#39;, JSON.stringify(pkgSolution, null, 4)); return gulp.src(\u0026#39;./config/package-solution.json\u0026#39;) .pipe(gulp.dest(\u0026#39;./config\u0026#39;)) }); let bumpRevisionTask = build.task(\u0026#39;bump-revision\u0026#39;, bumpRevisionSubTask); Finally add the line of code that will register your task as a pre-build task\nbuild.rig.addPreBuildTask(bumpRevisionTask); Now when you run gulp serve, your packages.json file will automatically increment the 4th position (revision)\nThat’s it! each time you build it will auto increment the last digit here. Once I decide that I’m satisfied in the changes I will reset the build to zero and then bump the major, minor or patch depending on the level of change.\nI find it useful to add in my projects, do you? Tell me what you think below.\nMore on NPM versioning # This is an older post that I came across while looking in to how to best version these SPFx projects. This gave some motivation and some better insight into how to leverage npm versioning inside my SPFx projects.\nhttps://n8d.at/blog/how-to-version-new-sharepoint-framework-projects/\n","date":"Aug 12, 2018","image":"/images/spfx-automatically-generating-revision-numbers/spfx-automatically-generating-revision-numbers-2018-08-12_14-25-27_hu_5f98f562fe14fe47.webp","permalink":"/2018/08/12/spfx-automatically-generating-revision-numbers/","section":"Post","summary":"I like automatic revision numbers while I’m developing. I don’t have to manually be making changes while pushing packages out and testing. While working on…","title":"SPFx Automatically Generating Revision Numbers","type":"posts"},{"content":"I’ve been renewed for Office Servers \u0026amp; Services MVP for 2018 – 2019!\n","date":"Aug 6, 2018","image":"","permalink":"/2018/08/06/ive-been-renewed-for-office-servers-services-mvp-for-2018-2019/","section":"Post","summary":"I’ve been renewed for Office Servers \u0026amp; Services MVP for 2018 – 2019!","title":"I’ve been renewed for Office Servers \u0026 Services MVP for 2018 – 2019!","type":"posts"},{"content":"Update 2/26/2020 – due to the comments I’ve posted a github project that you can check out to help. https://github.com/tom-daly/spfx-trim-ribbon\nWhat I’m about to show you is a technique that you could use to trim the ribbon and suite bar off an Office 365 – SharePoint site. If you worked with any previous on-prem version we had the SPSecurityTrimmedControl where we could set permissions string. I first learned of this from Dr. Z’s famous post, “How To Hide Ribbon From Users Without Edit Page Privilege”\nIn comes new modern SharePoint sites and pages where we don’t have master pages to work with. Yet the desire of some clients to remove that top bar for users with minimal permissions still remains. So what can we do? I have 2 ways that I can show you how to accomplish this. Let’s start with the easy method and I’ll save the more complex for another post.\nCreate an SPFx – Application Customizer # Create the basic hello world application customer. Click the link in the header to reference the Microsoft guidance. We are focused on the top placeholder for this example but any placeholder would work just fine. If you have an application customizer then skip this section\nNow that you have an application customizer with top placeholders you are ready for the good stuff. For this we’ll use PnP JS library to check user permissions. This is consider the easier method because the PnP JS core library does all the heavy lifting. It allows you to easily make a call given a Permission level. Trust me, doing this yourself requires in depth knowledge about the High/Low security permissions, converting decimals to binary and mapping permissions to a bit on a binary string. If you are feeling adventurous you can read some more details on André Lage’s post “It is SharePoint Permission call FullMask or “NearFullMask” in CSOM?”\nAdd PnP-JS to your project # Follow the guidance here “Getting Started: Install \u0026amp; Use” with the NPM Install\nNow in your application customizer .ts file you need to import PnP\nimport pnp from \u0026#34;sp-pnp-js\u0026#34;; inside the onInit() you must initialize the pnp.setup before you call your content placeholders. If you don’t do this then pnp won’t be ready to use inside the content placeholders.\n@override public onInit(): Promise\u0026lt;void\u0026gt; { Log.info(LOG_SOURCE, `Initialized ${strings.Title}`); return super.onInit().then(_ =\u0026gt; { pnp.setup({ spfxContext: this.context }); this.context.placeholderProvider.changedEvent.add( this, this._renderPlaceHolders ); this._renderPlaceHolders(); }); } This setups up PnP so it can do it’s work. My application customizer creates a sub component called Header and here is where I do my permission checking and trimming.\nprivate _renderPlaceHolders(): void { if (!this._topPlaceholder) { this._topPlaceholder = this.context.placeholderProvider.tryCreateContent( PlaceholderName.Top ); if (this._topPlaceholder) { if (this._topPlaceholder.domElement) { const element: React.ReactElement\u0026lt;IHeaderProps\u0026gt; = React.createElement(Header, {}); ReactDom.render(element, this._topPlaceholder.domElement); } } } } Insides Header.tsx I create a function that will trim the suite bar / ribbon. I am using pnp js to check for a particular security level and then hiding or showing. In my case below I’ve hidden the ribbon for everyone using CSS and then I unhide it here. You can use any of the PermissionKind enumeration listed here\nimport pnp, { PermissionKind } from \u0026#34;sp-pnp-js\u0026#34;; public componentDidMount(): void { this._trimSuiteBar(); } private _trimSuiteBar(): void { pnp.sp.web.usingCaching() .currentUserHasPermissions(PermissionKind.EditListItems) .then(perms =\u0026gt; { var suiteBar = document.getElementById(\u0026#34;SuiteNavPlaceHolder\u0026#34;); if (!suiteBar || !perms) return; //return if no suite bar OR perms not high enough suiteBar.setAttribute(\u0026#34;style\u0026#34;, \u0026#34;display: block !important\u0026#34;); }); } And there you have it. Suite Bar / Ribbon trimming on Modern Office 365 / SharePoint sites using an SPFx Application Customizer and PnP JS\n** Disclaimer ** I have to tell you that this is simply for information and demonstration purposes. Use at your own risk. It’s not recommended to trim the ribbon or suite bar as it provides functionality to users within the SharePoint site. This is obviously a hack to the product and I’m sure Microsoft would not like you to do this.\n","date":"May 14, 2018","image":"","permalink":"/2018/05/14/trimming-the-suite-bar-ribbon-on-modern-sharepoint-sites-in-office-365/","section":"Post","summary":"Update 2/26/2020 – due to the comments I’ve posted a github project that you can check out to help. https://github.com/tom-daly/spfx-trim-ribbon What I’m about …","title":"Trimming the Suite Bar + Ribbon on Modern SharePoint Sites in Office 365","type":"posts"},{"content":"This little script saves me thousands of keystrokes and hours of wasted time while building SPFx (SharePoint Framework) solutions. Generating a production level build is one of the most annoying parts of building SPFx solutions. In the Visual Studio world we could just hit a button and BAM! a package shot out the other end. Well, that is not entirely the case with SPFx. First I have to ‘gulp clean’ then ‘gulp build’ then ‘gulp bundle’ then ‘gulp package-solution’ then go to the folder. This gets old quick, at least for me it did. There were a few ways I tried to fix this and the simplest way was to make a batch file that would call all the commands. I’m sharing this with you so that you don’t have to do any experimentation or try writing your own.\nbuild-prod.cmd\ncls call gulp clean call gulp build --ship call gulp bundle --ship call gulp package-solution --ship call explorer .\\sharepoint\\solution\\ Put this file in the root of your SPFx project and run it in the command line. Of course you have to watch for errors. This is something I usually run one I know i have a clean + working build. Don’t forget to increment your build numbers 🙂 Happy coding!\n","date":"May 7, 2018","image":"/images/simple-build-script-for-the-sharepoint-framework/simple-build-script-for-the-sharepoint-framework-code-coding-computer-330771_hu_293b3afb5621bca5.webp","permalink":"/2018/05/07/simple-build-script-for-the-sharepoint-framework/","section":"Post","summary":"This little script saves me thousands of keystrokes and hours of wasted time while building SPFx (SharePoint Framework) solutions. Generating a production level …","title":"Simple Build Script for the SharePoint Framework","type":"posts"},{"content":"Starting February 14th, 2018 – Microsoft will push an update to a tiny, but very important, piece of code in all your master pages. They will be changing the default document model from Internet Explorer 10 mode to IE Edge mode.\nThis change will be completely rolled out by the end of February.\nHow will you be impacted?\nSome custom web part, master pages, or 3rd parties might rely on the document model as IE=10 compatible.\nKnown Issues (as of this writing):\n“Open with Explorer” does not work with IE Edge Steps to take:\nIf you know you have a custom masterpage and want to continue running in IE=10 compatible mode then you must make the change manually.\nInside the masterpage make the following change:\n\u0026lt;!-- comment this out, like so --\u0026gt; \u0026lt;!--\u0026lt;SharePoint:IECompatibleMetaTag runat=\u0026quot;server\u0026quot;/\u0026gt;--\u0026gt; \u0026lt;!-- add this --\u0026gt; \u0026lt;meta http-equiv=\u0026quot;X-UA-Compatible\u0026quot; content=\u0026quot;IE=10\u0026quot;/\u0026gt;\n","date":"Jan 16, 2018","image":"","permalink":"/2018/01/16/changes-to-default-document-mode-in-office-365-sharepoint-online/","section":"Post","summary":"Starting February 14th, 2018 – Microsoft will push an update to a tiny, but very important, piece of code in all your master pages. They…","title":"Changes to Default Document Mode in Office 365 / SharePoint Online","type":"posts"},{"content":"In this article we’ll pick up where we left off in Part 1 and begin the creation of a gulpfile.js. This file will provide the instructions to our build process. Gulp will be watching our files for changes and then publishing them to our SharePoint site. We are going to start out with monitoring a folder called “Style Library” that will deploy up to “Style Library”.\nCreating the gulpfile.js # In Visual Studio Code, under your project folder, select New File\nInside the gulpfile.js we need to add the follow code:\nThese are going to be the dependencies\nvar gulp = require(\u0026#34;gulp\u0026#34;); var spsave = require(\u0026#34;spsave\u0026#34;).spsave; var watch = require(\u0026#34;gulp-watch\u0026#34;); Now we’ll add references that are needed by spsave. We’ll create these files after we finish the gulpfile.js\nvar creds = require(\u0026#34;./creds.js\u0026#34;); var coreOptions = require(\u0026#34;./coreOptions.js\u0026#34;); Here we’ll add some variables to our folders \u0026amp; paths.\nThe paths variable will hold the locations for our local development files\nThe fileOptions object is more specific for spsave\nvar paths = { styleLibrary: [\u0026#34;./src/Style Library/**/*\u0026#34;] }; var fileOptions = { styleLibrary: { folder: \u0026#34;Style Library\u0026#34;, //folder inside SharePoint glob: paths.styleLibrary, //local folder base: \u0026#34;Style Library\u0026#34; //this remove the \u0026#39;Style Library\u0026#39; from the url } }; Now is our only function which pushes files into SharePoint given the configuration, credentials, and the file mapping from local to SharePoint\nvar copyToSharePoint = function(fileOptions) { return spsave(coreOptions, creds, fileOptions); }; Next we need to define a set of tasks, which I will explain from bottom up.\nWe create a task call “styleLibrary” which will call the copyToSharePoint function passing the fileOptions for the Style Library. We also define a “deploy” task which calls the “styleLibrary” task. When we define more functionality to “deploy” we will add those here to the array. Lastly we define the “default” task which will be our “watch” task. gulp.task(\u0026#34;default\u0026#34;, [\u0026#34;watch\u0026#34;]); gulp.task(\u0026#34;deploy\u0026#34;, [\u0026#34;styleLibrary\u0026#34;]); gulp.task(\u0026#34;styleLibrary\u0026#34;, function() { return copyToSharePoint(fileOptions.styleLibrary); }); For the final task we will define a “watch” that will monitor the Style Library and call copyToSharePoint using the specified options\ngulp.task(\u0026#34;watch\u0026#34;, function() { watch(paths.styleLibrary).on(\u0026#34;change\u0026#34;, function(file) { var changedFileOptions = fileOptions.styleLibrary; changedFileOptions.glob = file; copyToSharePoint(changedFileOptions); }); }); Creating creds.js file # In Visual Studio Code, under your project folder, select New File\nInside the creds.js we need to add the follow code:\nHere you will set the user account \u0026amp; password that spsave will use to upload the files\nmodule.exports = { username: \u0026#34;{USER}@onmicrosoft.com\u0026#34;, password: \u0026#34;{PASSWORD}\u0026#34; }; Creating coreOptions.js file # In Visual Studio Code, under your project folder, select New File\nInside the coreOptions.js we need to add the follow code:\nYou will need to set the parameters like the Site Url, whether you have check in enabled and what type also whether or not you want notifications for success/failures upon uploading.\nmodule.exports = { siteUrl: \u0026#34;https://{YOUR-SITE}.sharepoint.com\u0026#34;, notification: true, checkin: true, checkinType: 1 //0 minor, 1 major, 2 overwrite }; Creating the Folder Structure # Next we need to create new folders that we’ve mapped in the gulpfile.js\nIn Visual Studio Code, under your project folder, select New Folder\nUnder here we will create a src/Style Library folder. This folder is defined in our gulpfile.js and will be set to be watched for changes.\nDownload these files here: sample-project-step2\nRunning the task # At this point it’s important just to make sure everything is working. We will be running those gulp tasks that were created in the previous steps.\nFrom the terminal window you can run the following command:\ngulp You don’t need to specify anything else because we’ve set the default command to watch. Below you will see the expected output.\nNow let’s go ahead and create a test.txt file inside the Style Library folder. Inside the test.txt type any message.\nIf everything is correct then you will see the test.txt successfully get pushed into the target site in the specified location. Congratulations! you are now seeing the potential power of working on your desktop and remotely pushing files. This is just the tip of the iceberg as you can do much more however everything now relies on this foundation we’ve established to work properly.\nIf you receive an error message, please take a deep breath and read the message from the top down. It’s likely that your username and password is not 100% correct or the site url is wrong.\nIn the next article we will extend this file to upload additional types of assets such as: page layouts, master pages, publishing images.\nStay tuned for Part 3…\n","date":"Jan 5, 2018","image":"","permalink":"/2018/01/05/sharepoint-quick-deploy-build-process-using-node-gulp-sp-save-part-2/","section":"Post","summary":"In this article we’ll pick up where we left off in Part 1 and begin the creation of a gulpfile.js. This file will provide the…","title":"SharePoint Quick Deploy \u0026 Build Process Using Node, Gulp \u0026 SP Save – Part 2","type":"posts"},{"content":"New Year, New Job. 2018 Enter the Dragon\nFor those of you that don’t know me, I started a new gig this year. I recently joined SoHo Dragon NYC, where I still am a Office 365 \u0026amp; SharePoint consultant. There was a great opportunity to join their growing team in NYC. I’ll be helping to nurture and grow their Office 365 \u0026amp; SharePoint practice as well be a leader and mentor for some of the junior developers.\nThis wasn’t entirely an easy decision to start a new job. For many years (8 exactly) I was very comfortable in my previous role as a SharePoint Developer were I sat and just developed requirements that were handed to me. I worked with tons of interesting clients and solved lots of real world problems. I worked with a growing team and some of my lifelong friends. But I stayed longer than I ever thought. I’ve always had a fire inside that demands challenge and requires a level of unpredictability (why I like consulting).\nI decided that it was time to make a change. Over the past few years I’ve worked with SoHo Dragon’s CTO and owner Peter Ward on different community engagements. We would car-pool on weekend trips to SharePoint Saturday’s in Boston, Baltimore other nearby states. I’ve witnessed SoHo Dragon mature as a company during that time and I liked the direction that they are going. It wasn’t until the end of 2017 when I decided that it was time to moved on from my previous work and join them. I am looking forward to the new clients and new challenges that this year brings.\n","date":"Jan 3, 2018","image":"/images/new-year-new-job-2018-enter-the-dragon/new-year-new-job-2018-enter-the-dragon-pexels-photo-713663_hu_b7d20bfcb91d5e68.webp","permalink":"/2018/01/03/new-year-new-job-2018-enter-the-dragon/","section":"Post","summary":"New Year, New Job. 2018 Enter the Dragon For those of you that don’t know me, I started a new gig this year. I recently…","title":"New Year, New Job. 2018 Enter the Dragon","type":"posts"},{"content":"For the past year or so I’ve been developing and building branding assets in Visual Studio Code for my Office 365 projects. Not only just creating and building assets in VS Code but I’ve build out a custom build and deployment process using node, gulp, sp save. I’ve switched from building traditional sandboxes solutions on my SharePoint VM using the full blown Visual Studio, to developing in VS Code on my main desktop continuously build/deploy to my developer tenant. Not only does this save endless hours in the deployment process alone, but the build/testing allows me to instantly see my work with minimal delay.\nYou don’t have to follow my process verbatim but if you have little experience with Node and using Gulp or other plugins this article can help walk you through what I am doing and how I am using in day to day. This process is most beneficial for me in branding projects where I can quickly develop and deploy branding assets [master pages, css, javascript, page layouts, images]. I can also use this process for deployment of those assets to other environments, like staging or production. I typically use this method in tandem with other methods such as SharePoint Patterns and Practices PnP. Where I use node/gulp/spsave during development and PnP for deployment to production. PnP has higher functionality in the sense you can deploy lists/libraries, custom site columns, content types…. and the node/gulp/spsave really just builds \u0026amp; moves assets. The spsave modules has the ability to upload files into SharePoint with meta data, but it has limitations and I rely on PnP for the initial deployment of assets.\nIn this 4 part post I’ll cover how to get your environment setup, setting up the build scripts, using the build scripts, and lastly pairing this with SharePoint PnP. Let’s get started\nPart 1 – Installing the Tools \u0026amp; Plugins # This first step will walk through installing all the tools and plugins that I use. If you think you already know this then feel free to jump ahead to Part 2.\nVisual Studio Code # In the example I will be using Visual Studio Code, https://code.visualstudio.com/. It’s not mandatory that you use visual studio code. You can use any other web development IDE that you want. I will also be running the build scripts inside the visual studio terminal. If your IDE has a built in terminal you could use that or just use the windows command line.\nInstalling Node # Browse to the Node.js site and download the latest LTS version. If you have this installed already then skip ahead. What we are doing works on other versions as well.\nhttps://nodejs.org/en/\nInstalling Gulp # Gulp is the task runner that we will be using to automate our build tasks. If you haven’t use Gulp before it can seem overwhelming. For part 1 all you need to know is that it’s purpose is to perform tasks. Tasks can be almost anything you can think of. For our purpose, our tasks will be copy file to SharePoint. In the next section we’ll go through creating some really basic tasks. By chaining a bunch of really simple tasks we’ll be able to increase our productivity. If you want more information you can visit: https://gulpjs.com/. Okay, so lets get started.\nAfter you’ve install Node.js, open up a command line terminal using the either of the 2 following commands:\nStart -\u0026gt; Run -\u0026gt; cmd [enter] or WindowsKey+R -\u0026gt; cmd [enter] at the command line terminal we will run the following command:\nnpm install -g gulp This will install gulp globally within the node.js environment. We install it globally so that our local projects can use it. Leave the command line open for the next couple of steps.\nCreating a Project Folder # Before we can continue we need to setup a project folder and initialize a project. From the command line enter the following commands:\ncd\\ md sample-project cd sample-project npm init The last command will initialize a project. You can pretty much [enter], [enter], [enter] through the file. It’s not really important at this time. This init command will ultimately create a packages.json file. This file will hold all information about the packages that we will be using in our projects.\nLaunch Visual Studio Code # from the command line terminal enter the following command:\ncode . Inside VS Code launch the terminal with the following command:\nctrl-` # From the command line inside VS Code run the following command:\nnpm install gulp --save-dev This will reinstall gulp again but locally inside our project folder. You will notice a ‘node_modules’ folder created with some new folders/files in there. You might notice a ton of files and that’s OK. Installing a package installs all the packages that are requirements as well. Lastly the –save-dev will write the dependency into the packages.json file.\nPackages.json # After you ran ‘npm init’ in the first few steps it created a packages.json file. This file is used to track the project dependencies. For our purposes we are using this simply as a build tool and not as a packages manager. Npm will handle adding dependencies using –save-dev or –save. Dev dependencies are once that are required to build the project so we will be using that exclusively throughout the articles. The two other primary reasons package dependencies are being tracked in this file: 1) to keep a record of the version of the installed package 2) when you check code in you don’t commit the node_modules, running ‘npm init’ will downloaded all the packages in the packages.json\nInstalling gulp-watch # Gulp has the ability to watch for file changes however it has a problem with newly added files. To fix this we are going to use gulp-watch to watch for new created files without having to stop/start gulp.\nfrom the command line terminal enter the following commands:\nnpm install gulp-watch --save-dev Installing spsave # spsave is an amazing module for node that allows us to upload files into SharePoint. That might not seem impressive but it handles all the funky stuff for us, like authentication, meta data, and various library properties [check in/out]. It does exposes an easy way for us to set our configuration and the away it goes sending your files from your desktop development environment into SharePoint Online. You can visit the website for more specific settings that what we’ll cover here. https://www.npmjs.com/package/spsave.\nfrom the command line terminal enter the following commands:\nnpm install spsave --save-dev # At this point we’ve installed everything that we need in order to start configuring Gulp. In the next article we’ll walk through the steps of creating our project structure and a gulpfile.js.\nStay tuned for Part 2…\n","date":"Dec 4, 2017","image":"","permalink":"/2017/12/04/sharepoint-quick-deploy-build-process-using-node-gulp-sp-save-part-1/","section":"Post","summary":"For the past year or so I’ve been developing and building branding assets in Visual Studio Code for my Office 365 projects. Not only just…","title":"SharePoint Quick Deploy \u0026 Build Process Using Node, Gulp \u0026 SP Save – Part 1","type":"posts"},{"content":"I found this so strange that I had to post something about it. It’s really easy to change the site logo for a Modern Communication site. The menus are very straight forward to change the logo, you can see in the video below.\nHowever when working on a Modern Team site if you use the same menu it does not allow you to change the site logo the same way. I’ve tried this on two different tenants and have two different results. I believe both are on standard release.\nTenant 1 – missing Site Logo\nTenant 2 – has Site Logo but errors – Oops! Something went wrong. Please try again in a few minutes.\nThe error is just a lie, it will never work. I put in a ticket with Microsoft on this and they said that it won’t work that way. For a Modern Team Site you have to go to the Site Assets folder and upload the site logo there. It must be name ‘__siteIcon__.jpg’ and must be a jpg for it to work.\nUpload your new file and overwrite the existing file. Wait a few seconds and hit CTRL-F5 to clear the cache and there you have it.\n","date":"Nov 5, 2017","image":"/images/changing-the-site-logo-on-modern-team-site/changing-the-site-logo-on-modern-team-site-2018-02-04_22-23-11_hu_e1b911c29c72c588.webp","permalink":"/2017/11/05/changing-the-site-logo-on-modern-team-site/","section":"Post","summary":"I found this so strange that I had to post something about it. It’s really easy to change the site logo for a Modern Communication…","title":"Changing the Site Logo on Modern Team Site","type":"posts"},{"content":"Introduction\nI’m typically asked quite often on how I can attach global CSS or javascript like jQuery to my SharePoint / Office 365 site. There are a number of ways in order to ‘hookup’ or ‘connect’ CSS or JavaScript to your Office 365, SharePoint site. I’m going to review what your options are and what my favorite (easiest) way is. Let’s begin.\nOption 1 [Only Publishing Enabled]\n+Alternative CSS URL\nPros:\nNo special tools needed other than the browser of choice.\nVery easy to attach a file using the interface.\nThe CSS can be applied to every sub site under\nDifferent CSS can be applied per site\nCons:\nCan only link 1 file in this slot. Once it’s used you can’t link anymore.\nManual process, not scriptable or repeatable.\nFrom Site Settings\nUnder Look and Feel, Click on Master Page\nAt the bottom, Expand Alternate CSS URL, Click Specify a CSS file to be used, locate a CSS file to attach, then Click OK\nOption 2 [Not Recommended]\n+Using Content Editor / Script Editor Web Parts (CEWP)\nPros:\nQuickly add CSS / JavaScript to a page by directly copy/paste into these web parts.\nCons:\nWorks only on that particular page, not a global solution.\nIf you accidentally delete the web part, you will lose your customizations. (not if you host the customizations in an external files and link it as content source (CEWP only))\nManual process, not scriptable or repeatable.\nOption 3 [Best for Multiple Environments / Repeatable / Scriptable]\n+Using SharePoint PnP\nAdd-PnPJavaScriptBlock – used to add a block of JavaScript to execute\nAdd-PnPJavaScriptLink – used to add links to JavaScript file\nBelow is some PowerShell PnP used to attach CSS and JavaScript files. In order to reference CSS you must load it via JavaScript. To do that I’ve borrowed some JavaScript that the PnP JS library uses. You can download that here\n## CSS FILES Add-PnPJavaScriptLink -Name \u0026quot;loadStyles\u0026quot; -Url \u0026quot;$($configObject.siteUrl)/Style Library/js/cssLoader.js\u0026quot; -Sequence 0 -Scope Site Add-PnPJavaScriptBlock -Name \u0026quot;googleFonts\u0026quot; -Script \u0026quot;cssLoader.loadStylesheet(`\u0026quot;https://fonts.googleapis.com/css?family=Lato:400,700|Roboto+Condensed:400+700|Alegreya+Sans+SC`\u0026quot;, false);\u0026quot; -Sequence 1 Add-PnPJavaScriptBlock -Name \u0026quot;fontAwesome\u0026quot; -Script \u0026quot;cssLoader.loadStylesheet(`\u0026quot;$($configObject.siteUrl)/Style Library/vendor/font-awesome/css/font-awesome.min.css`\u0026quot;, false);\u0026quot; -Sequence 1 Add-PnPJavaScriptBlock -Name \u0026quot;styles\u0026quot; -Script \u0026quot;cssLoader.loadStylesheet(`\u0026quot;$($configObject.siteUrl)/Style Library/css/styles.css`\u0026quot;, true);\u0026quot; -Sequence 2 ## JS FILES Add-PnPJavaScriptLink -Name \u0026quot;jQuery\u0026quot; -Url \u0026quot;$($configObject.siteUrl)/Style Library/vendor/jQuery/jquery-3.2.1.min.js\u0026quot; -Sequence 0 -Scope Site Add-PnPJavaScriptLink -Name \u0026quot;moment\u0026quot; -Url \u0026quot;$($configObject.siteUrl)/Style Library/vendor/moment/moment.min.js\u0026quot; -Sequence 1 -Scope Site Add-PnPJavaScriptLink -Name \u0026quot;resize\u0026quot; -Url \u0026quot;$($configObject.siteUrl)/Style Library/vendor/resize/jquery.ba-resize.min.js\u0026quot; -Sequence 1 -Scope Site Pros:\nEasy to apply JavaScript to your site (takes a little bit more for CSS).\nBuilds a repeatable method to attach JavaScript \u0026amp; CSS to your site. Great for repeatable process and source control (as you can store these files)\nYou can use the C# PnP or PowerShell PnP\nCons:\nTakes a little bit more than 1 command, need to establish a connection.\nNeed to be comfortable using powershell or C#.\nNeed to have the powershell modules installed.\nNo native command to attach CSS, they recommend using JavaScript to attach CSS.\nOption 4 [My Favorite for Quick \u0026amp; Easy Applications]\n+SP-Editor / Chrome Only\nDownload SP Editor plugin from the Chrome Store\nOnce you have the plugin installed and navigated to your SharePoint site, hit F12 to open the developer tools bar.\nClick on the SharePoint tab and then click ScriptLinks. Here you can enter the sequence number to order your CSS references or JavaScript links. You also enter a site collection relative url from JavaScript only. After you enter that information, Click on the Add ScriptLink dropdown and select either current web or site collection\nPros:\nSuper Fast \u0026amp; Easy\nQuickly apply CSS or JavaScript to a site or web (sub site) using the browser\nCons:\nRequires Chrome [only a con if your organization doesn’t allow you to have Chrome]\nManual process, not scriptable or repeatable.\n","date":"Oct 29, 2017","image":"/images/the-easiest-way-to-attach-css-or-javascript-to-your-office-365-or-sharepoint-site/the-easiest-way-to-attach-css-or-javascript-to-your-office-3-91c98696_hu_cfbc7852de8ff031.webp","permalink":"/2017/10/29/the-easiest-way-to-attach-css-or-javascript-to-your-office-365-or-sharepoint-site/","section":"Post","summary":"Introduction I’m typically asked quite often on how I can attach global CSS or javascript like jQuery to my SharePoint / Office 365 site. There…","title":"The Easiest Way to Attach CSS or JavaScript to your Office 365 or SharePoint Site","type":"posts"},{"content":"Awarded Microsoft Servers and Services MVP for 2018!\nI am so happy to announce that I’ve been awarded Microsoft MVP in Office Servers and Services (essentially Office 365). It feels amazing to receive this recognition and be included among some best and brightest out there.\nMany people ask how do you get to become an MVP, well for me it was a long time in the making. I have put in 100’s of hours in attending SharePoint Saturday’s, Conferences, and User Groups outside regular work hours. I’ve stayed up late nights tweeting, blogging my findings, and sharing examples/projects within github all while working with Office 365 and SharePoint. I put myself out there and present content for online webinars, sharing at local user groups and presenting at SharePoint Saturdays and Conferences around the country. I also have been running the New Jersey Office 365 \u0026amp; SharePoint User Group as it mutate over the past ten years, from Princeton @ Infragistics now to Iselin @ Microsoft. Finding speakers and sponsors to bring content to our area and facilitate networking. Most of the time laying out my own money for website, pizza \u0026amp; refreshments. I have successfully run 9 SharePoint Saturdays in NYC which are the largest in the country (Approx 500 people), 2 SharePoint Saturday New Jerseys (~125ppl), 1 Azure Bootcamp, 2 Office 365 Bootcamps, and I’ve assisted which helping out in some degree on many other events.\nHow do you get MVP? I can’t say 100% because it’s different for everyone. While I’m glad to be recognized these are the activities that I do regardless. The main reasons is that I doing it. I like sharing things that I find with people. I like free events that people can come and learn, connect with experts and their peers. These experiences you can’t get from a video or reading a article.\nMicrosoft thank you for this award! I hope that I can continue to serve my community and be thanked for doing so!\n","date":"Jul 1, 2017","image":"","permalink":"/2017/07/01/awarded-microsoft-servers-and-services-mvp-for-2018/","section":"Post","summary":"Awarded Microsoft Servers and Services MVP for 2018! I am so happy to announce that I’ve been awarded Microsoft MVP in Office Servers and Services…","title":"Awarded Microsoft Servers and Services MVP for 2018!","type":"posts"},{"content":"I stumbled across an issue last week where a client was reporting that the custom branding broke the print functionality of their Nintex forms. I made the master page so I already know that I didn’t chop out anything from the masterpage, it might be hidden but it’s still there. I wondered how can just some CSS break the page. Perhaps it was some of that JavaScript code that we wrote? Or maybe a conflicting version of jQuery? …Nope. After a few hours or trial and error (showing all hidden zones, removing all js and css, then slowly putting things back in) I found the issue with the references to Google Fonts.\nThis is my normal way of linking multiple google fonts. Nintex Print to PDF doesn’t seem to like the first line. It specifically doesn’t like the ‘,’ in the url. If you removed everything after “400” then it would work fine. Our design requires all 4 weights of this specific font. In other designs we’d have multiple google fonts with multiple weights being requested.\n\u0026lt;link href=\u0026#39;//fonts.googleapis.com/css?family=Open+Sans:400,300,700,600\u0026#39; rel=\u0026#39;stylesheet\u0026#39; type=\u0026#39;text/css\u0026#39;/\u0026gt; \u0026lt;!--[if lte IE 8]\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;//fonts.googleapis.com/css?family=Open+Sans:300\u0026#34; /\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;//fonts.googleapis.com/css?family=Open+Sans:400\u0026#34; /\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;//fonts.googleapis.com/css?family=Open+Sans:700\u0026#34; /\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;//fonts.googleapis.com/css?family=Open+Sans:800\u0026#34; /\u0026gt; \u0026lt;![endif]--\u0026gt; Simple fix is to link to each file independently (not the most efficient since you are making 4 calls to google instead of 1) Ultimately you likely won’t notice any difference.\n\u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;//fonts.googleapis.com/css?family=Open+Sans:300\u0026#34; /\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;//fonts.googleapis.com/css?family=Open+Sans:400\u0026#34; /\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;//fonts.googleapis.com/css?family=Open+Sans:700\u0026#34; /\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;//fonts.googleapis.com/css?family=Open+Sans:800\u0026#34; /\u0026gt; You can see in the image below that the fonts are all loading at the same time anyway taking 16ms except for that one. This is an example on just one load time. You will get different results each time. Google fonts are also cached so it’s likely the users will already have a copy in the browser.\n","date":"Mar 12, 2017","image":"/images/print-to-pdf-not-working-on-nintex-form/print-to-pdf-not-working-on-nintex-form-nintext-print-to-pdf_hu_8e8293970a71a146.webp","permalink":"/2017/03/12/print-to-pdf-not-working-on-nintex-form/","section":"Post","summary":"I stumbled across an issue last week where a client was reporting that the custom branding broke the print functionality of their Nintex forms. I…","title":"Print to PDF not working on Nintex Form","type":"posts"},{"content":"During my last project we were asked to create a fairly simple site template on Office 365. I typically cringe when I hear the word site definition / site template or web template. The reason goes way back to the beginning of my career with SharePoint. One of my first projects I had to upgrade a site definition from 2007 to 2010. I’m not going to complain too much but let’s just say that there was barely any documentation on how to accomplish this. For a n00b I struggled through it and I guess I completely it successfully.\nFast forward to today… the last time I created a web template for Office 365 I was able to build the site and then “Save as Template”. This worked because it was not a publishing site, but a team site. That template saved successfully by it created some monstrous WSP with tons of files, folders, feature and XML definitions. To make any changes to the template I had to scan through those XML definitions. The experience was not fun to say the least.\nI was researching the deep and dark interwebs on “the best method to create site templates on Office 365”. The result of that research showed that the shift is towards remote site provisioning using either your client machine, a provider hosted add-in or another method like web job, remote timer job, Azure function. Basically anything capable of running remote client object model code to programmatically build out your site.\nI chose to perform remote site creation via PnP-Powershell [See GitHub Site]. The PnP-Powershell team has been doing fantastic work. They currently a couple of ways to do remote site provisioning.\nprovision from a live site (as a template) provision from a xml definition – this can be exported from a live site and saved for reuse Unfortunately I wasn’t able to get either of those options to work for me because of a know issue that they are working on. [Apply-PnPProvisioningTemplate : Value cannot be null. Parameter name: stream #1010]. I decided that I was going to continue to use the PnP-Powershell commands to programmatically create the site instead of using some of their provisioning commandlets.\nIf your just getting started then I suggest checking out these following resources on remote provisioning\nIntroduction to Office 354 PnP-PowerShell Site provisioning techniques and remote provisioning in SharePoint 2013 Site provisioning in the SharePoint add-in model Getting Started # The first thing you’ll need to do is visit the GitHub site for PnP-PowerShell – https://github.com/SharePoint/PnP-PowerShell. You’ll need to make sure that you have the pre-requisites installed. You also need to install the PnP commandlets with the command below\nInstall-Module SharePointPnPPowerShellOnline -AllowClobber In the following paragraphs I’m going to demonstrate the code that was ultimately used as the solution.\nMy code template required the following items:\nLists Announcements Calendar Web Parts on Home Page Announcements Custom Display (using display template) Calendar Document Library This should give you a basic idea of what we need to accomplish.\nThe Commands # Next up is the current set of commands that I ran to provision the new site.\n#the root site collection where the subsites will be created $url = \u0026#34;https://bandrdev.sharepoint.com\u0026#34; #the parameters for my new web we are going to create $siteTitle = \u0026#34;My New Site\u0026#34; $siteUrl = \u0026#34;my-new-site\u0026#34; $siteTemplate = \u0026#34;CMSPUBLISHING#0\u0026#34; #get and save your O365 credentials $cred = Get-Credential -Message \u0026#34;Enter SPO credentials\u0026#34; #connect to the root site collection first Connect-PnPOnline –Url $url –Credentials $cred #create the new web site on the root site collection using the Publishing template and inherit the parent navigation New-PnPWeb -Title $siteTitle -Url $siteUrl -Template $siteTemplate -InheritNavigation #connect to the new web site using the stored credentials Connect-PnPOnline –Url $url/$siteUrl –Credentials $cred #create 2 new lists Calendar using Events template and Announcements using the Announcements template New-PnPList -Title \u0026#34;Calendar\u0026#34; -Template \u0026#34;Events\u0026#34; -OnQuickLaunch New-PnPList -Title \u0026#34;Announcements\u0026#34; -Template \u0026#34;Announcements\u0026#34; -OnQuickLaunch #create a new view for the Announcements list called Rollup with a row limit of 4, a custom query to change the order, and a set of fields to display Add-PnPView -List \u0026#34;Announcements\u0026#34; -Title \u0026#34;Rollup\u0026#34; -RowLimit 4 -Query \u0026#34;\u0026lt;OrderBy\u0026gt;\u0026lt;FieldRef Name=\u0026#34;\u0026#34;Created\u0026#34;\u0026#34; Ascending=\u0026#34;\u0026#34;False\u0026#34;\u0026#34;/\u0026gt;\u0026lt;/OrderBy\u0026gt;\u0026#34; -Fields @(\u0026#34;Title\u0026#34;,\u0026#34;Body\u0026#34;,\u0026#34;Created\u0026#34;) #get the current web server relative url $currentWebServerRelativeUrl = Get-PnPWeb | %{ $_.ServerRelativeUrl } #get the current web home page for modification $homePage = \u0026#34;$currentWebServerRelativeUrl/Pages/default.aspx\u0026#34; #read the web part xml of the custom view we created. This will then be imported to the home page $announcementsWpXml = GetWebPartXml \u0026#34;$currentWebServerRelativeUrl/Lists/Announcements/Rollup.aspx\u0026#34; #check out the homepage for editting Set-PnPFileCheckedOut -Url $homePage #take the announcements roll up view xml and add it as a web part to the homepage in the Top Column Zone first position Add-PnPWebPartToWebPartPage -ServerRelativePageUrl $homePage -Xml $announcementsWpXml -ZoneId \u0026#34;TopColumnZone\u0026#34; -ZoneIndex 1 #grab all the web parts off the home page $allWebParts = Get-PnPWebPart -ServerRelativePageUrl $homePage #we know there is only 1 right now so pull that $wp = Get-PnPWebPart -ServerRelativePageUrl $page -Identity $allWebParts[0].Id #update the web part properties of the Announcements web part to use my custom JSLink file so that I can control the look of it Set-PnPWebPartProperty -ServerRelativePageUrl $homePage -Identity $wp.Id -Key \u0026#34;JSLink\u0026#34; -Value \u0026#34;~siteCollection/Style Library/jslink/team-announcements.js\u0026#34; #update the web part properties of the Announcements web part to have no chrome Set-PnPWebPartProperty -ServerRelativePageUrl $homePage -Identity $wp.Id -Key \u0026#34;ChromeType\u0026#34; -Value 2 #get the view xml of the calendar $calendarWpXml = GetWebPartXml \u0026#34;$currentWebServerRelativeUrl/Lists/Calendar/calendar.aspx\u0026#34; #take the view xml of the calendar and add it as a web part to the home page in the Left Column Zone first position Add-PnPWebPartToWebPartPage -ServerRelativePageUrl $homePage -Xml $calendarWpXml -ZoneId \u0026#34;LeftColumnZone\u0026#34; -ZoneIndex 1 #get the view xml of the document library (this is on the site already as part of CMSPUBLISHING#0) $documentsWpXml = GetWebPartXml \u0026#34;$currentWebServerRelativeUrl/Documents/Forms/AllItems.aspx\u0026#34; #take the view xml of the document library and add it as a web part to the home page in the Right Column Zone first position Add-PnPWebPartToWebPartPage -ServerRelativePageUrl $homePage -Xml $documentsWpXml -ZoneId \u0026#34;RightColumnZone\u0026#34; -ZoneIndex 1 #check in the home page Set-PnPFileCheckedIn -Url $homePage -Comment \u0026#34;Provisioned by Powershell\u0026#34; This worked for what we needed however I wanted to point out a few things\n1. There is a problem is you run this in a script. If you run this as part of a .ps1 it seems to throw an error but doesn’t appear to cause an issue. I write the out as $null an continue. If you type it into the command line it doesn’t throw an error. [Related:Add-SPOView breaks View Menu #320]\nAdd-PnPView -List \u0026#34;Announcements\u0026#34; -Title \u0026#34;Rollup\u0026#34; -RowLimit 4 -Query \u0026#34;\u0026lt;OrderBy\u0026gt;\u0026lt;FieldRef Name=\u0026#34;\u0026#34;Created\u0026#34;\u0026#34; Ascending=\u0026#34;\u0026#34;False\u0026#34;\u0026#34;/\u0026gt;\u0026lt;/OrderBy\u0026gt;\u0026#34; -Fields @(\u0026#34;Title\u0026#34;,\u0026#34;Body\u0026#34;,\u0026#34;Created\u0026#34;) -ErrorAction SilentlyContinue \u0026gt; $null 2. Chrome Type Property – It’s not clearly listed anywhere and it took some playing around till I got this to work. I’m assuming since it accepted 2 that it’s the PartChromeType Enumeration (i added the red numbers in the picture below)\nSet-PnPWebPartProperty -ServerRelativePageUrl $homePage -Identity $wp.Id -Key \u0026#34;ChromeType\u0026#34; -Value 2 Summary # In end the I was happy with the results. I found out shortly after speaking with a colleague, Josh Carlisle aka our provisioning guru, that this could be all nicely packaged up and put into an Azure function. This way that someone doesn’t have to run the code on their desktop. It could be implemented in a cloud way where you just need to trigger that function to run… possibly using Flow where you just enter the site creation information into a list and it fires away or a custom form. Bottom line is that I’m excited to really see the potential of the PnP initiatives. It has some glitches with some of the commandlets during the time that I tried it but I can see how it will become very powerful once it fully matures.\n","date":"Mar 6, 2017","image":"/images/creating-a-site-template-web-using-pnp-powershell-provisioning/creating-a-site-template-web-using-pnp-powershell-provisioni-46c3cd0f_hu_210b747420c08dc0.webp","permalink":"/2017/03/06/creating-a-site-template-web-using-pnp-powershell-provisioning/","section":"Post","summary":"During my last project we were asked to create a fairly simple site template on Office 365. I typically cringe when I hear the word…","title":"Creating a Site Template (Web) using PnP-Powershell Provisioning","type":"posts"},{"content":"Here’s a quick little snippet of CSS if you are trying to hide the Newsfeed, OneDrive or Sites from the Waffle Menu in SharePoint 2016. If you are looking to do this in the Ribbon for SharePoint 2013 then check out my other posting – /2016/03/05/hide-newsfeed-onedrive-andor-sites-from-the-suite-bar-ribbon-area-for-sharepoint-2013/\nWhere do you put this? There are a number of places that you can put it. The best option would be in the Alternate CSS file [I’m going to get around to posting this cause I haven’t found a great article explaining this to newcomers]. You could also just add it to the MasterPage but that’s not the cleanest method.\nHide Newsfeed # Before:\nCSS Snippet:\n/* Hiding Newsfeed */ .o365cs-nav-appTile[id=\u0026#39;O365_AppTile_Newsfeed\u0026#39;] { display: none; } After:\nHide OneDrive # Before:\nCSS Snippet:\n/* Hiding OneDrive */ .o365cs-nav-appTile[id=\u0026#39;O365_AppTile_Documents\u0026#39;] { display: none; } After:\nHide Sites # Before:\nCSS Snippet:\n/* Hiding Sites */ .o365cs-nav-appTile[id=\u0026#39;O365_AppTile_Sites\u0026#39;] { display: none; } After:\nBonus # After you hide 2 items you’ll get a space like this. Add the next line of CSS to tighten it up.\nCSS Snippet:\n/* To remove the minimum width off the size of the box */ .o365cs-rsp-off.o365cs-newAL-off.o365cs-nav-navMenu { min-width: 0; } After:\nHide all 3 – Newsfeeds, OneDrive, \u0026amp; Sites + remove the min width of the box # CSS Snippet:\n.o365cs-nav-appTile[id=\u0026#39;O365_AppTile_Newsfeed\u0026#39;], .o365cs-nav-appTile[id=\u0026#39;O365_AppTile_Documents\u0026#39;], .o365cs-nav-appTile[id=\u0026#39;O365_AppTile_Sites\u0026#39;] { display: none; } .o365cs-rsp-off.o365cs-newAL-off.o365cs-nav-navMenu { min-width: 0; } Thanks for reading!\n","date":"Feb 24, 2017","image":"/images/branding-hide-newsfeed-onedrive-andor-sites-from-the-waffle-for-sharepoint-2016/branding-hide-newsfeed-onedrive-andor-sites-from-the-waffle--7addccf1_hu_139cd399e079af2c.webp","permalink":"/2017/02/24/branding-hide-newsfeed-onedrive-andor-sites-from-the-waffle-for-sharepoint-2016/","section":"Post","summary":"Here’s a quick little snippet of CSS if you are trying to hide the Newsfeed, OneDrive or Sites from the Waffle Menu in SharePoint 2016….","title":"Hide Newsfeed, OneDrive and/or Sites from the Waffle for SharePoint 2016","type":"posts"},{"content":"Over the course of the past 6 months I’ve found myself using Visual Studio Code as my goto html/css/javascript IDE. Much of the time I will use it in conjunction with VS2013 or VS2015. I find it easier to deal with when writing html, sass/css, or javascript. I’ve basically moved completely away from other IDE’s such as Sublime Text. I know they can both accomplish the same thing more or less. Almost a year ago when I first started using VS Code I wasn’t an immediate fan. In fact I might have said that I would never use it. Those days are long behind me because and VS Code is my #1 IDE for front end development. Here are a list of my top productivity tips and favorite extensions. If you’ve been using VS Studio then you most likely already know about these items but for those not aware please take a few minutes to see what I have to say.\nAuto Save This is such a simple thing that can save time. It was not enabled by default when I installed so be sure to enable it. I found it very useful to during development and using a live reload or deploying up to my site for testing.\nIntegrations with Externals Tools via Tasks Having the tasks running write there inside VS Code can save time from switching back and forth between command prompts or staring at another screen. You can get the results of your build, deploy, or test right inside VS Code.\nEmmet Snippets Right out of the box VS Code supports Emmet snippets. These are simply amazing and a major time saver for those that write handfuls of HTML. I’ve been able to churn through HTML once I found out of these snippets. It actually opened up my eyes to the other types of snippets out there. I never cared before but I do not because it’s actually saved me time. It even works with many CSS properties.\nAnother great post on the power of emmet snippets [I’ve found VS Code doesn’t support all these here]: https://www.smashingmagazine.com/2013/03/goodbye-zen-coding-hello-emmet/\nCode Formatting I like that this is already there. I’ve tried many of the other extensions and it just doesn’t work as easy and simply as the out of the box functionality. I’m OK with the no frills code formatting at the moment.\nvscode-icons There has to be some reason VS Code doesn’t come with any icons for the files. Well doesn’t matter because there are a number of VS icons in the marketplace. I’ve chosen the following because they look great and they have a ton of downloads and a high rating…so why not.\njshint I prefer to have hinting on both my javascript and css. I’m currently using this plugin by Dirk Baeumer which has been working well for me.\nHTML CSS Class Completion VS Code has some level of ability to be able to do this already but not in the capacity that this plugin brings to the table. It will scan your files inside your workspace and provide you with intellisense on the available classes.\njQuery Code Snippets Here is another great plugin that can save you time from looking up the proper implementation of various jQuery functions. I have most of them memorized by now but this would have been great starting out. Another plus is that it can help maintain consisting when using the snippets.\nEven more snippets!\nSo I’m not going to go on and on about all the snippets. I will stop here but as you can see there are many more. I encourage you to go out and download some extension, rate and provide feedback to the authors. Let them know that if you like it or if you have suggestions. All of the items in the marketplace are open source so you can go direct to the repo and post issues or even download the code and fix it yourself!\nI’ll be presenting this topic as one of the 6 lightning talks next month at the New Jersey Office 365 User Group. This blog is my topic for that event hence the purpose of writing it.\n","date":"Nov 29, 2016","image":"/images/visual-studio-code-productivity-tips-favorite-extensions/visual-studio-code-productivity-tips-favorite-extensions-7433d69c_hu_40da5cceb4eda942.webp","permalink":"/2016/11/29/visual-studio-code-productivity-tips-favorite-extensions/","section":"Post","summary":"Over the course of the past 6 months I’ve found myself using Visual Studio Code as my goto html/css/javascript IDE. Much of the time I…","title":"Visual Studio Code Productivity Tips \u0026 Favorite Extensions","type":"posts"},{"content":"There are a number of already built in helper (or utility) classes in Office 365 and SharePoint 2013. These classes can be very useful when you are building new components. You can skip writing CSS for some of the most basic things by simply using their classes on your objects. Below is the CSS that is already in your Office 365 or SharePoint 2013 site. Below are the snippets copied from the “CORE” CSS file of the items I found useful.\nHorizontal Alignment # .ms-alignRight { text-align:right; } .ms-alignLeft { text-align:left; } .ms-alignCenter { text-align:center; } check it out in Plunker\nVertical Alignment # I found it odd that they don’t have a vertical-align: bottom;\n.ms-verticalAlignTop { vertical-align:top; } .ms-verticalAlignMiddle { vertical-align:middle; } .ms-verticalAlignBaseline { vertical-align:baseline; } check it out in Plunker\nPosition # I noticed that they have absolute listed twice in the “CORE” css files\n.ms-positionRelative { position:relative; } .ms-positionAbsolute { position:absolute; } check it out in Plunker\nFloat # .ms-floatRight { float:right; } .ms-floatLeft { float:left; } check it out in Plunker\nDisplay # .ms-displayBlock { display:block; } .ms-displayInline { display:inline; } .ms-displayInlineBlock { display:inline-block; } check it out in Plunker\nTable # I use this one the most and I absolutely love it. Only caveat is that you cannot specify columns, it’s a very strict table. If you need to split a cell then you would have to put in an entire new table. This is used to turn ‘div’s into table like objects for positioning and to control the display. I also typically pair this with the vertical positioning. You can see Microsoft use this in #s4-titlerow.\n.ms-table { display:table; } .ms-tableRow { display:table-row; } .ms-tableCell { display:table-cell; } check it out in Plunker\nGeneral # .ms-hide { display:none; } .ms-visibilityHidden { visibility:hidden; } .ms-clear { clear:both; } check it out in Plunker\nText # They don’t many sizes to work with here\n.ms-textXLarge { font-family:\u0026#34;Segoe UI Semilight\u0026#34;,\u0026#34;Segoe UI\u0026#34;,\u0026#34;Segoe\u0026#34;,Tahoma,Helvetica,Arial,sans-serif; font-size:1.46em; } .ms-textLarge { font-family:\u0026#34;Segoe UI Semilight\u0026#34;,\u0026#34;Segoe UI\u0026#34;,\u0026#34;Segoe\u0026#34;,Tahoma,Helvetica,Arial,sans-serif; font-size:1.15em; } .ms-textSmall, .ms-textXSmall { font-size:.9em; } .ms-bold { font-weight:bold; } .ms-italic { font-style:italic; } .ms-smallIndent { margin-left:20px; } .ms-indent { margin-left:25px; } check it out in Plunker\nText Wrapping # .ms-noWrap { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .ms-forceWrap { word-wrap:break-word; } .ms-normalWrap { white-space:normal; word-wrap:normal; } check it out in Plunker\nSizing # .ms-fullWidth { box-sizing:border-box; -moz-box-sizing:border-box; -webkit-box-sizing:border-box; width:100%; } .ms-fullHeight { box-sizing:border-box; -moz-box-sizing:border-box; -webkit-box-sizing:border-box; height:100%; } .ms-fillBoxFull { box-sizing:border-box; -moz-box-sizing:border-box; -webkit-box-sizing:border-box; height:100%; width:100%; } .ms-fillBox { height:100%; width:100%; } check it out in Plunker\nLists # .ms-noList ul,.ms-noList ol,.ms-noList { list-style-type:none; padding-left:0px; } check it out in Plunker\nPadding/Margin # put this on your items that you want to eliminate padding or margin\n.ms-padding0 { padding:0px; } .ms-margin0 { margin:0px; } Cursor # If you have an actionable ‘div’ tag or element other than an ‘a’ tag and you want to set the cursor to look like a link\n.ms-cursorDefault { cursor:default; } .ms-cursorPointer { cursor:pointer; } check it out in Plunker\n","date":"Oct 16, 2016","image":"/images/built-in-helper-classes-inside-office-365-sharepoint-2013/built-in-helper-classes-inside-office-365-sharepoint-2013-e978f5c4_hu_d059b02b219cf0f6.webp","permalink":"/2016/10/16/built-in-helper-classes-inside-office-365-sharepoint-2013/","section":"Post","summary":"There are a number of already built in helper (or utility) classes in Office 365 and SharePoint 2013. These classes can be very useful when…","title":"Built-in Helper Classes Inside Office 365 \u0026 SharePoint 2013","type":"posts"},{"content":"You may or may not already know that SharePoint has the ability to do full width background images. If you’ve ever applied one of those wacky composed looks then you would see that some of them have full width background images included. You can even apply your own through customizing a composed look. What I’m about to demonstrate is completely different from that. In the follow article I will demonstrate how you can hijack the same exact mechanism that SharePoint is already using to make your background image full width. There is no need for full width CSS or JavaScript tricks to make the image go full width, however we do need to include some CSS so that we can inject our image in it’s place.\nIf you’ve ever inspected the full width background image from a composed look then you will notice that there is a class on thetag called ‘ms-backgroundImage’ with some css applied to that using the background-image property set to nothing. We are going to take that over.\nAll you need to do is add some CSS and you can apply this via ‘Alternative Style Sheet’ or if you have a custom CSS file then make your edit there. If not just do an internet search for applying an alternative style sheet and I’m sure you’ll find 100’s of results.\n.ms-backgroundImage { background-image: url(\u0026#39;[REPLACE THIS WITH THE URL TO THE FILE]\u0026#39;); } If you wanted to do it via JavaScript you could do something like this.\ndocument.getElementsByTagName(\u0026#34;body\u0026#34;)[0].style.backgroundImage = \u0026#34;url(\u0026#39;[REPLACE THIS WITH THE URL TO THE FILE]\u0026#39;)\u0026#34;; That’s essentially it. No fussing with any additional CSS or JavaScript based plugins.\n**Note: I noticed that the image sometimes popped through on my dialog boxes. To eliminate that you could apply this line of CSS. I’m using important so that it’s gone and has no chance of showing itself in that dialog box. **\n.ms-dialog body { background-image: none !important; } ","date":"Sep 16, 2016","image":"/images/hijack-sharepoints-built-in-full-width-background-image/hijack-sharepoints-built-in-full-width-background-image-burglar-alarm_hu_66c0f16a75df12d3.webp","permalink":"/2016/09/16/hijack-sharepoints-built-in-full-width-background-image/","section":"Post","summary":"You may or may not already know that SharePoint has the ability to do full width background images. If you’ve ever applied one of those…","title":"Hijack SharePoint’s Built-in Full Width Background Image","type":"posts"},{"content":"This is an exciting time! The much anticipated SharePoint Framework Preview was released yesterday. And guess what … they are essentially Add-ins /Apps. Right now just the web parts piece is released and the end result is an improvement over app parts. The most noteworthy part is that the web parts are now on the page as opposed to being inside of an iframe! They have a couple more improvements like being able to be fully responsive, having web part properties. They mention that new developments will be added frequently and they are targeting a release time frame of early 2017.\nThe biggest hurdle that I can see for traditional SharePoint developer is the tooling. If you are not comfortable with using a command line, Node.js, NPM, Yeoman or Gulp then you should invest a large chunk of time to familiarize yourself with those technologies. I believe that having a base knowledge of the “What” is actually happening when you type “yo @microsoft/sharepoint” is important. Most modern day front end web developers are already comfortable with these things. For the traditional SharePoint developer most of these things will be foreign and seemingly difficult to grasp at first (for some).\nSo you’ve visited the SharePoint Framework Developer Preview Release git-hub and you’re following the steps but you find yourself scratching your head. What did you miss? What is all this stuff? Where do you begin to even get started? # First off don’t feel bad.. most people are seeing this for the first time just like you.\nJust the other day I discussed the front end development build process of node/gulp/npm to other day to our Senior SharePoint developers and even they were confused. Some even got pissed off and mad at Microsoft. I can remember the comments clearly, “COMMAND LINE? What year is this?”. I’ve embraced the change and I can see the good things that can come out of it. Getting back to point of this article. I recommend that you take the time to learn about the new tools that they are asking us to adopt. There are ton’s of free online resource on YouTube alone… However if you have or can get your job to pay for a Pluralsight you will access to premium content with some of the top authors (this will be more important for the SharePoint work)\nOnline resources (free) to help you get familiar with for Node/Gulp/TypeScript/Yeoman # Node.js Fundamentals NodeJS – NPM Package Manager – Tutorial 2 Gulp.js Build System #1 – Fundamentals Gulp – The Basics TypeScript/ES6 Module Syntax Intro Yeoman – Read first paragraph If you haven’t even started to develop apps or client side web parts using JavaScript then you need to take a look at resources geared towards connecting to SharePoint lists and libraries using the REST end points or the client side JavaScript object model. There are some course that I highly recommend.\nPluralsight Courses for SharePoint Client Object Model # David Mann http://www.pluralsight.com/courses/developing-sharepoint-2013-javascript http://www.pluralsight.com/courses/developing-sharepoint-2013-javascript-part2 http://www.pluralsight.com/courses/developing-sharepoint-2013-javascript-part3 Rob Windsor http://www.pluralsight.com/courses/sharepoint-2013-client-object-model-rest Rob Windsor, \u0026amp; Sahil Malik http://www.pluralsight.com/courses/sp2010-client-object-model Can I just use Visual Studio? PLEASE!@#!@ Do I need to learn all the front end tooling? # The framework can be intimidating for the people not comfortable with the technologies listed. For the most part you really just need to learn a couple of things and you can get by. TypeScript is not one of those things. Good news for those most comfortable working in Visual Studio. They have documentation on how to get setup via Visual Studio 2015 (Update 3) here. The bad news is that is still uses Node.js and the Gulp build tasks to compile and build the project. We will just have to wait and see if the process is improved with future releases.\nOk I got all that… # After you’ve covered all this then you should walk through the examples. The documentations is very good at explaining how to get everything installed and get up and running with your first Hello World web part. My only piece of advice is to read through everything 1 step at a time. It took me a few reads before I was able to get the first web part to load inside SharePoint.\nFinal Thoughts: # Get an Office 365 Developer Account Learn the fundamental technologies in play – Node.js, Gulp, Yeoman, Node Package Manager (npm) Become a TypeScript master Changes, Updates, Fixes will be coming frequently with a release time frame of 2017 (so they say) Preview is missing a few things that a traditional deployment would have Examples on how to deploy files to SharePoint / not CDN Examples on how to deploy assets (List Definitions, Content Types, Site Columns) =\u0026gt; Notes on Solutions Packaging ","date":"Aug 18, 2016","image":"/images/sharepoint-framework-developer-preview-release-where-do-i-begin/sharepoint-framework-developer-preview-release-where-do-i-be-0abec9c6_hu_11740929f7f2d57d.webp","permalink":"/2016/08/18/sharepoint-framework-developer-preview-release-where-do-i-begin/","section":"Post","summary":"This is an exciting time! The much anticipated SharePoint Framework Preview was released yesterday. And guess what … they are essentially Add-ins /Apps. Right …","title":"SharePoint Framework Developer Preview Release – Where do I begin?","type":"posts"},{"content":"I was just thinking about this issue one day and realized that I could just override the default image but if the user decides that they wanted to change it later via site settings they could.\nWhy do I love this trick? # You don’t have to set a Site Logo via Site Settings \u0026gt; Title, description, and logo For On-Prem solutions I would set this via code For Office 365 you would typically have to do this manually OR powershell to set it across It’s pure CSS trickery, no change to any markup! This will work across the board On-Prem/Office 365…. No code…. Manually setting the Logo you can override this trick For those subsites that don’t want to inherit the logo they can manually set the logo via the Site Settings \u0026gt; Title, description, and logo Once the logo is set in the default it will override this trick Get to the trick already!!!! # Add the following lines of CSS. Only a couple of notes to make this actually work.\nimg[src*=\u0026#39;siteIcon\u0026#39;] { display: block; -moz-box-sizing: border-box; box-sizing: border-box; background: url(‘/Style Library/mySiteLogo.png’) no-repeat; /* Path to the site logo */ width: 180px; /* Width of new image */ height: 64px; /* Height of new image */ padding-left: 180px; /* Equal to width of new image */ } .ms-siteicon-img, .ms-siteicon-a { max-height: none; max-width: none; } Only 2 important detail for this trick to work.\nYou must set the actual image size must equal the information above for this trick to work. DON’T use ‘siteIcon’ in the filename. How does this work?\nThis trick works because it uses CSS to catch onto the field where the ‘src’ contains ‘siteIcon’. This case catches both the themed site icon on the publishing site and the site icon on the team site. The CSS that performs the replacement of the image comes from https://css-tricks.com/replace-the-image-in-an-img-with-css/ by Marcel Shields. You can read more about that there.\nPublishing Site Logo Team Site Logo You’ll notice that the site logo will change\nYou might also noticed that in the settings there is no logo set but yet your custom logo will show. [This will go away if you set the logo here just like an override]\nGear -\u0026gt; Site Settings -\u0026gt; Look and Feel -\u0026gt; Title, description, and logo\n","date":"Aug 9, 2016","image":"/images/change-default-sharepoint-site-logo-css-trick-great-for-office-365/change-default-sharepoint-site-logo-css-trick-great-for-offi-3993950e_hu_3a87e5abc979a6d6.webp","permalink":"/2016/08/09/change-default-sharepoint-site-logo-css-trick-great-for-office-365/","section":"Post","summary":"I was just thinking about this issue one day and realized that I could just override the default image but if the user decides that…","title":"Change Default SharePoint Site Logo CSS Trick – Great for Office 365!","type":"posts"},{"content":"It really is super simple and requires I line of CSS per item. If you want to hit them all or any future ones you could hide the whole element. I’m posting this b/c I was looking for a quick fix to do this and all the blogs make you change master pages or something else wacky. I didn’t have mine enabled and really was being lazy. So here it is.\nHide OneDrive\n#suiteLinksBox .ms-core-suiteLink-a[id$=\u0026#39;ShellDocuments\u0026#39;] { display: none; } Hide Newsfeeds #suiteLinksBox .ms-core-suiteLink-a[id$=\u0026#39;ShellNewsfeed\u0026#39;] { display: none; } Hide Sites\n#suiteLinksBox .ms-core-suiteLink-a[id$=\u0026#39;ShellSites\u0026#39;] { display: none; } Get rid of them all in one shot\n#suiteLinksBox .ms-core-suiteLink-a[id$=\u0026#39;ShellDocuments\u0026#39;], #suiteLinksBox .ms-core-suiteLink-a[id$=\u0026#39;ShellNewsfeed\u0026#39;], #suiteLinksBox .ms-core-suiteLink-a[id$=\u0026#39;ShellSites\u0026#39;] { display: none; } Get rid of the anything that could possible go in there #suiteLinksBox { display: none; } There you have it folks – simple css to just pop those items right off the page.\n","date":"Mar 5, 2016","image":"/images/hide-newsfeed-onedrive-andor-sites-from-the-suite-bar-ribbon-area-for-sharepoint-2013/hide-newsfeed-onedrive-andor-sites-from-the-suite-bar-ribbon-eed6fbff_hu_4ee4db07a69d2986.webp","permalink":"/2016/03/05/hide-newsfeed-onedrive-andor-sites-from-the-suite-bar-ribbon-area-for-sharepoint-2013/","section":"Post","summary":"It really is super simple and requires I line of CSS per item. If you want to hit them all or any future ones you…","title":"Hide Newsfeed, OneDrive and/or Sites from the Suite Bar – Ribbon Area for SharePoint 2013","type":"posts"},{"content":" Introduction # This all started as a request from a client to hide only certain users from the people search. I’ve decided on this route because the management interface already exists to update user profiles and it would be very little work to add a user profile property, get it crawled and use that as the filter in the people search results webpart. Below I will explain how I was able to accomplish this in Office 365 but the same would work for a SharePoint 2013 on prem.\nUpdate 1/28/2017 – I was able to test and in order for Office 365 search to pick up a newly created user profile property you must set the value of that property in at least 1 user profile. Running the reindex-user.sp1 script located here https://github.com/wobba/SPO-Trigger-Reindex will also ensure that that a full crawl happens (relatively soon somewhere between 0-8hrs possibly?)\nUpdate 1/25/2017 – There have been many comments about Office 365 not picking up the new field. I am still evaluating the exact steps but this takes time. I recently was able to get Office 365 to pickup the custom field last night using https://github.com/wobba/SPO-Trigger-Reindex \u0026gt; reindex-users.ps1 script. I believe that this is what made it happen but i need more time to test.\nHow To # In Office 365 -\u0026gt; SharePoint Administration -\u0026gt; User Profiles\nClick “Manage User Properties” under the People Group\nClick “New Property”\nSet the following properties (everything else leave be)\nName: HideFromPeopleSearch\nDisplay Name: HideFromPeopleSearch\nPolicy Setting: Optional\nDefault Privacy Settings: Everyone\nSearch Settings -\u0026gt; Alias [checked]\n-\u0026gt; Indexed [checked]\nClick “OK”\nWait some amount of time for O365 to index the User Profiles and retrieve this new field.\n!!! for O365 users !!! – You must set this property on at least 1 user profile before it will be picked up by the crawl. I also recommend running this script ‘reindex-users.ps1’ \u0026gt; https://github.com/wobba/SPO-Trigger-Reindex will also ensure that that a full crawl happens (relatively soon somewhere between 0-8hrs possibly?)\n_** for on prem users – kick off a FULL search crawl of your people content source**\n_\nYou’ll know it’s ready when you can find the new Property in the Search Schema, Crawled Properties.\nFrom the Admin Center click “search” in the left hand menu\nClick “Manage Search Schema”\nClick “Crawled Properties”\nEnter “Hide” in the Crawled properties search box, then click the green arrow\nYou will see the property appear in the search results when the property has been successfully crawled.\nNext you will need to create a “Managed Property”, Click On the “Managed Properties” link\nClick “New Managed Property”\nEnter the following information:\nProperty Name: HideFromPeopleSearch\nType: Text\nQueryable: Checked\nRetrieveable: Checked\nAdd Property Mapping -\u0026gt; People:HideFromPeopleSearch\nClick Add A Mapping\nEnter “Hide” in the search box, click the Find button\nSelect “People:HideFromPeopleSearch”, click the OK button\nLeave all the other properties as-is, click OK\n**You will now have to wait until Office 365 runs a full crawl on your site for the property to become available in search. When this action takes place is unknown. (I’ve head 4 hours)\n**\n_** for on prem users – kick off a FULL search crawl of your site content source**\n_\nNext go to the people search page\nEdit the page and Edit the Web Part Properties of the People Search Core Results\nClick Change Query\nIn the Property Filter drop down select –Show all managed properties–\nSelect HideFromPeopleSearch\nSelect “Not equals” then “Manual Value”\nEnter “True” for the Manual Value\nNext Click Add property filter\nClick OK\nClick OK in the People Search Core Results, Web Part Properties pane\nSave, Check-In \u0026amp; Publish your page – All Set!\nTo manage users it’s as easy as using the User Profile Service Application, searching for a user’s profile and checking a box to save. Watch the video if you are note sure about that – http://screencast.com/t/5VGSLCt5N\nRemember it won’t be instantly removed – it will take a crawl of the people source for those changes to make it to the site!!!\n","date":"Jan 16, 2016","image":"/images/hiding-people-from-people-search/hiding-people-from-people-search-3792703838_01dc23210e_o_hu_8c68bc4e357b23cd.webp","permalink":"/2016/01/16/hiding-people-from-people-search/","section":"Post","summary":"Introduction This all started as a request from a client to hide only certain users from the people search. I’ve decided on this route because…","title":"Hiding People from People Search","type":"posts"},{"content":" Introduction # In my last post I talked about making the SharePoint navigation render in a Bootstrap friendly way. I suggested that you could do this by changing the markup of the navigation using an ASP:Repeater to display the nodes. This works great if you’re on an on-prem environment but on Office 365 you cannot use this method because the masterpage cannot contain code blocks. If you haven’t read that it might be worth taking a look at – Bootstrap Responsive Navigation in SharePoint .\nThis post will demonstrate an Office 365 safe version that use JavaScript, jQuery, and REST to retrieve the navigation from SharePoint’s navigation provider. Once we have that we’ll render that out to the screen client side. With this approach we can use the out of the box Managed Meta Data navigation provider for the navigation, and we can control the exact rendering so that it fits Bootstrap’s model for a navigation bar.\nThe code can be downloaded directly from my git-hub account – https://github.com/tom-daly/sp2013-bootstrap-nav\nGetting Started # It’s very simple to get started you only need to do a few things.\nAdd the link to the topNavigation.js in your SharePoint masterpage In the Master Page, add the container below where the navigation will be pushed into. You will need to determine where you want the navigation to go, I’ll have a full example at the very end. Switch the SharePoint Navigation to use the Managed Meta Data Navigation\nIn Site Settings Look and Feel -\u0026gt; Navigation Once you complete those steps you’ll have your navigation displayed in that container.\nOther Details # Changing Rendering # The rendering of the menu is defined in the renderNavigationNodes function. This is currently the format the Bootstrap likes. If you visit the Bootstrap website and take a look at the first example\nhttp://getbootstrap.com/components/#navbar\nThe red box is exactly what the code is injecting. So you would wrap all that however you want it to appear. Follow the examples there are plenty out there.\n2 Level Flyouts # Currently this supports only 2 levels, a top level and 1 flyout. This is just what Bootstrap v3.3.5 supports and that’s what I’m sticking with currently. If you want more levels then it’s up to you to figure that part out. It can be done and there are other 3rd parties implementing 3rd or 4th level flyouts after the fact. The code is recursive and will support as many levels as you have, you just need to handle the front end portion.\nThe REST Call # The query that I am using can be seen below. The /web/navigation/topbar endpoint – flat out sucks. It won’t show if a node is hidden and it didn’t behave. This is the only one I could get to work reliably.\nChanging Target Container # If you want to change where the navigation goes, instead of “#my-top-navigation” then you can edit the topNavigation.js file and at the bottom change the selector to another ID preferably.\nSample Files # I have a few more sample files that might be of use to look at.\nsharepoint-bootstrap-resets.css – Sample CSS file (helps with some Bootstrap/SharePoint resets)\n**remove masterpage file**\nConclusion # The whole goal of this script was to have a way for an Office 365 site to use the Bootstrap navigation. Although there are still limitations it’s doable. And you don’t even necessarily need to touch the master page. You could attach the scripts and css via other methods which I won’t go in to. So if you’re an Office 365 purist who doesn’t want to customize the master page you would want to take an approach to this. I hope this helps someone out there. If there are problems with the script you can report the issues through the github page. https://github.com/tom-daly/sp2013-bootstrap-nav/issues\n","date":"Oct 13, 2015","image":"/images/bootstrap-navigation-for-office-365-sharepoint-2013-using-jquery-and-rest/bootstrap-navigation-for-office-365-sharepoint-2013-using-jq-1693ee81_hu_37d13ffe3c7386f.webp","permalink":"/2015/10/13/bootstrap-navigation-for-office-365-sharepoint-2013-using-jquery-and-rest/","section":"Post","summary":"Introduction In my last post I talked about making the SharePoint navigation render in a Bootstrap friendly way. I suggested that you could do this…","title":"Bootstrap Navigation for Office 365 \u0026 SharePoint 2013 using jQuery and REST","type":"posts"},{"content":" Updated # 10-5-2016:\nIncluded link to javascript version of bootstrap navigation Updated the navigation code to detect in active -\u0026gt; Contribution by Olly in comments below. Introduction # Many of my most recent branding projects have all been responsive. Clients are building sites that they want to work on desktops, tablets, \u0026amp; phones. If you’ve ever tried to deal with this request and SharePoint is your platform then you know what a handful this can be. For all my responsive branding projects I always go to Bootstrap. If you don’t know what that is then this article is not for you. We will not going into the details of implementing Bootstrap or the issues / fixes to do that. If you’re looking for cookie cutter bootstrap in SharePoint then I recommend that you check out Responsive SharePoint.\nThis article we will simply focus on the navigation. We will be replicating the standard out of the box (OOTB) SharePoint navigation and formatting it in a way that works with Bootstrap 3.0. I’ll provide you with the code snippet and an explanation of how to implement, and what is happening. It should leave you with the functionality of the Bootstrap’s collapsible navigation.\nOur main problems with the SharePoint navigation are:\nSharePoint out of the box navigation is NOT responsive SharePoint out of the box navigation doesn’t work well with touch enabled devices It has hover actions on the navigation, and hover doesn’t work on touch devices Prerequisites # The following need to be reference in your site.\njQuery Bootstrap 3.0 On-Prem SharePoint environment w/ access to the Masterpage You can see the references these in my master page SORRY SharePoint Onliner’s and O365’s – this won’t work b/c you can’t use code blocks in the MasterPage\nCheck out my other post for a JavaScript version that replaces the navigation – https://github.com/tom-daly/sp2013-bootstrap-nav\nThe Navigation Code # You can view the html markup that Bootstrap requires in order to work properly – http://getbootstrap.com/components/#navbar . The OOTB SharePoint navigation does its own thing so it would never work with Bootstrap. In this approach we’ll use the SharePoint top navigation provider but we’ll create our own navigation markup using asp repeaters. The following code will only work for 2 levels deep, it could be modified to work on X number of levels. You would just have to keep nesting repeaters. I am using ASP repeaters in the master page with no code behind in this approach. If you wanted to you could write a user control and simplify what goes into the master page but for me no C# code means less things that could break. There is one minor issue with this approach – see the Known Issues section.\nDownload Snippet Here # Take a look at how this renders, much better.\nAnd when the screen hits the break point. It collapses and you have the mobile friendly menu. The break point is set in the bootstrap CSS (I believe it is 768px). The menu below is also\nAnd the drop down\nThe Code Explained # You can skip this part if you don’t care what the code snippet actually does. I’ll break it down into parts to help you understand so you can make changes to it.\n1) It’s up to you to determine where you are going to put the navigation inside your masterpage # In my particular case below I’ve created my own custom header where I put the navbar. I then turn the visibility off on the PlaceHolderTopNavBar (line 543)\n2) Looking at just the very high level bootstrap nav html # 3) The Root Level Repeater # The first level I consider the root level. It contains the root navigation node typically “Home”. Below is the best representation of how the data comes back from the topSiteMap navigation provider. Each color is a level. The children of “Home” are the main level. This is similar to the OOTB, you might have seen the first node that isn’t in the navigation provider but just shows up anyway.\nNow for the root level. In this screen shot you can see I render the root node then have a second repeater for its children nodes. The html is fairly simple for the first node and gets more involved for the next level.\n4) The First Level Repeater # This level gets a bit more interesting because now we have two different classifications of nav nodes, with children and without children. We have to handle each a slight bit different and I’ll explain. If you have children we need to specify a different class to have a drop down. Next it needs a nested repeater to handle the next level (2nd level). If there are no children we can simply just render the navigation node just as we did with the root level. I use the ChildNodes.Count to determine which to hide or show. You might notice that the\nli’s line 487 \u0026amp; 507, have some other attributes like data-node-count and data-node-index and are REALLY long. I’ll explain that in the next section. 4a) First Level # Let’s examine the first the one with children. They are both the same except for the visible attr. We are going to focus on the following attributes, data-node-count, data-node-index, \u0026amp; class.\ndata-node-count – is not needed, but this will tell you how many sibling nodes are detected. I used this to help generate the if statements for class.\ndata-node-index – is not needed, but this will tell you the index of the node in amongst it’s current siblings. I used this to help generate the if statements for class.\nclass – this is basically a fancy edition I added which provides a first-node and last-node class. Again you’ll notice this one has “dropdown” in the class, the other\nwon’t because it doesn’t have children and doesn’t need a drop down.\nThe first node of a set will have the class=“dropdown first-node nav-node”\nThe middle nodes would have the class=”dropdown nav-node”\nThe last node would have the class=”dropdown last-node nav-node”\nclass=\u0026#39;\u0026lt;%# Container.ItemIndex == ((SiteMapNode)((RepeaterItem)Container.Parent.Parent).DataItem).ChildNodes.Count–1 ? \u0026#34;dropdown last-node nav-node\u0026#34; : (Container.ItemIndex == 0 ? \u0026#34;dropdown first-node nav-node\u0026#34; : \u0026#34;dropdown nav-node\u0026#34;) %\u0026gt;\u0026#39;\u0026gt; 5) The Second Level Repeater # The second level repeater code gets a little simpler. This is our last level and we don’t consider if the node has children or not. We just render the value of the node and leave it at that. If you wanted to go 3, 4 or 5 levels essentially it’s just copy what we’ve already done but I wouldn’t go more than 2 levels. Bootstrap 3.0 removed support for multilevel dropdowns, citing usability issues as the cause. You would be on your own if you decided to go for more levels. There are 3rd party plugins available to assist Bootstrap 3.0 with multi levels.\nConclusion # That explains how I was able to utilize the SharePoint out of the box top navigation provider and build a new menu with ASP repeaters to behave responsively and work with Bootstrap 3.0. This is a simple enhancement that I’ve been re-using on many on of my On-Prem responsive projects. Unfortunately this doesn’t not work for SharePoint Online or O365 as the Masterpage does not allow code blocks. If you are on O365 then I recommend using a javascript based approach – check out another approach here – /2015/10/13/bootstrap-navigation-for-office-365-sharepoint-2013-using-jquery-and-rest/\n","date":"Jun 28, 2015","image":"/images/bootstrap-responsive-navigation-in-sharepoint/bootstrap-responsive-navigation-in-sharepoint-Bootstrap-3-1_hu_70351f6dafd93eb0.webp","permalink":"/2015/06/28/bootstrap-responsive-navigation-in-sharepoint/","section":"Post","summary":"Updated 10-5-2016: Included link to javascript version of bootstrap navigation Updated the navigation code to detect in active -\u0026gt; Contribution by Olly in …","title":"Bootstrap Responsive Navigation in SharePoint","type":"posts"},{"content":" Introduction # I recently came across an interesting blog – http://www.n8d.at/blog/office-365-icon-font-documentation/ which mentions O365 icons. This was back in February so when I attempted to locate these fonts so I could use them in some of my work. I could not located the file on the CDN that was mentioned. I kind of shrugged it off for a while but recently needed the icons again. So I went digging inside O365 and I found them. They are there if you want to use them. I’ll show you what classes to use. I also pulled the files which I’ll provide a link to at the end of the blog so you can download them and use them and not have to worry about CDN’s.\nI found all the O365 icon inside my O365 Portal. I downloaded the web fonts and a portion of the css file that has the code for the icons. Using this approach you would be locked into the current icon. Microsoft is likely to continually update the icon \u0026amp; css, so this set would eventually become stale. If there ever is an official CDN I’d switch to that. If you’re on office 365 you don’t need to link to any fonts or css. You are able to just take advantages of the icons immediately you just need to look at my chart to see the classes or determine the code for the css content. If you don’t understand anything about using web fonts or what office 365 icons are, read the blog I mentioned above. That should bring you right up to speed.\nInstallation # copy the css \u0026amp; font folder to your on prem site link to the css file in your masterpage or page (adjust path as needed) you can implement one of the icons using something like this below i have additional styles for color, font size, and spacing. view the source to check that out Explanation of the Grid # the orange tiles are icons that are available by using the css:after { content: ‘code’; } or if you copy the supplemental css above the blue tiles are icons that have classes in O365. you can use those classes in directly on your o365 site without attaching any styles or fonts the black tiles are dead, meaning they don’t show or are not in the font package but may be in the official css and have not been purged. The concept of using fonts for icons is nothing new and if you are not exactly familiar with that check out Font Awesome or Glyphicons. The concept is that fonts will scale nice, the are light weight and fully loaded once when you hit the site. You can easily resize them and re-color them. No need for messing around with images.\nWhere I found the files # Fonts\nhttps://outlook.office365.com/owa/prem/16.0.772.13/resources/styles/fonts/office365icons.svg\nhttps://outlook.office365.com/owa/prem/16.0.772.13/resources/styles/fonts/office365icons.eot\nhttps://outlook.office365.com/owa/prem/16.0.772.13/resources/styles/fonts/office365icons.ttf\nhttps://outlook.office365.com/owa/prem/16.0.772.13/resources/styles/fonts/office365icons.woff\nCSS\nhttps://prod.msocdn.com/16.00.0791.004/en-US/css/shellg2corecss_f727a58f.css\nDownload Files Here # Screenshot of all the possible icons –\u0026gt; see full preview site above for a live example # # Download Files Here # Thanks for checking this out\n","date":"Jun 24, 2015","image":"/images/office-365-icons/office-365-icons-Office-365-logo_hu_171e34bf8deba0f0.webp","permalink":"/2015/06/24/office-365-icons/","section":"Post","summary":"Introduction I recently came across an interesting blog – http://www.n8d.at/blog/office-365-icon-font-documentation/ which mentions O365 icons. This was back in …","title":"Office 365 Icons","type":"posts"},{"content":"These are a few of my favorite and go to JavaScript libraries. These tend to work well in SharePoint with no problem. This is my personal list that I use\n**Updates**\n[9/6/2016] – Removed unsemantic added bootstrap (superior grid)\nGrid # http://getbootstrap.com/ Console Log # https://github.com/patik/console.log-wrapper Glyphs # All – http://fortawesome.github.io/Font-Awesome/ Weather – http://erikflowers.github.io/weather-icons/ O365 – http://www.n8d.at/blog/office-365-icon-font-documentation/ Carousel # Owl Carousel – http://owlgraphic.com/owlcarousel/index.html#demo slick – http://kenwheeler.github.io/slick/ jCycle2 – http://jquery.malsup.com/cycle2/ backFlip – https://github.com/backflip/jquery-carousel (doesn’t use .clone() – good for use in ajaxPanel or where you don’t want cloned elements) Cookies # jquery.cookie https://github.com/carhartl/jquery-cookie Click Outside # http://benalman.com/code/projects/jquery-outside-events/examples/clickoutside/ Mutate / Resize # http://www.jqui.net/demo/mutate/ (had issue with zip, use github files) http://benalman.com/code/projects/jquery-resize/examples/resize/ Smart Resize – http://www.paulirish.com/2009/throttled-smartresize-jquery-event-handler/ Select Box (supports Multi) # Sumo Select https://github.com/HemantNegi/jquery.sumoselect Time / Date # Moment – http://momentjs.com/ Datejs – http://www.datejs.com/ Truncating Text # jQuery dotdotdot… http://dotdotdot.frebsite.nl/?utm_source=dlvr.it\u0026amp;utm_medium=twitter [THIS DIDN’T WORK WITH br’s] Truncate – https://github.com/pathable/truncate Layouts (masonry type) # Isotope – http://isotope.metafizzy.co/ ** use imagesLoaded for images – https://github.com/desandro/imagesloaded Notify / Alerts # Notify.js – http://notifyjs.com/ Flavr – http://codecanyon.net/item/flavr-flat-jquery-popup-dialog/7021021 Modal # Zurb Reveal – http://foundation.zurb.com/docs/components/reveal.html – [Needs adjustment for s4-workspace, but otherwise good] Scroll Bars # jScrollPane – http://jscrollpane.kelvinluck.com/ SharePoint Specific # SPServices – https://spservices.codeplex.com/ ","date":"May 7, 2015","image":"/images/my-favorite-javascript-libraries-and-jquery-plugins/my-favorite-javascript-libraries-and-jquery-plugins-59a06d66_hu_f3530a83284f84ed.webp","permalink":"/2015/05/07/my-favorite-javascript-libraries-and-jquery-plugins/","section":"Post","summary":"These are a few of my favorite and go to JavaScript libraries. These tend to work well in SharePoint with no problem. This is my…","title":"My Favorite JavaScript Libraries and jQuery plugins","type":"posts"},{"content":"My new favorite thing is creating all my style sheets with SASS. SASS makes writing my style sheets faster, easier, cleaner, and more reusable. Don’t ask me why I chose SASS, but I liked it just a little better than LESS even though they are very similar. You can go do research for yourself and decide which you’d like to use. SASS – http://sass-lang.com/ LESS – http://lesscss.org/.\nSASS is nothing new however it is very difficult to work with when using with SharePoint 2010/2013 .. or Office 365. Why is it difficult? Because SASS needs to be compiled to CSS and none of your typical SharePoint tools do that.\nCurrent Scenarios: # If you are working on O365 you might be working with SharePoint Designer which won’t do anything for you. Maybe you have Visual Studio you can use Web Essentials or Mindscape Web Workbench. Both of these will compile the SASS to CSS You can use a stand alone compiler like Koala, Propos, \u0026amp; Scout (for Windows) … but configuring them to work can be tricky. You can use an online compiler – SassMeister | The Sass Playground! , but then you will be doing a lot of copy / pasting / uploading. This article is going to show you how to use Sublime Text 2 on your desktop, to write and compile SASS, which will automatically be saved to O365. No copy / paste or uploading needed!\nGeneral Approach # Map network drive to O365 site Use a desktop editor to create SASS Use a desktop compiler to create the CSS Use Scout to monitor CSS changes locally and copy file to network drive Pre-Requisites # 1. Java v7 – http://www.java.com/en/download [Scout has issue with any other version, https://github.com/mhs/scout-app/issues/173]\n2. O365 Site\nStep 1 – Install Sublime Text 2 \u0026amp; SASS Builder # 1. go to http://www.sublimetext.com/2 and download and install\n2. go to https://github.com/jaumefontal/SASS-Build-SublimeText2 (Follow Instructions there skip to step 3)\nInstalling Ruby # Install Ruby library (for windows) – http://rubyinstaller.org/downloads/\ncheck box to add Ruby executables to your PATH Start Command Prompt with Ruby\nIn the console type the following\ngem install sass IF you get this error Take a deep breath – you need to download the cert and add it to your RubyGem’s certificate directory.\nDownload the new PEM file AddTrustExternalCARoot-2048.pem in the ruby command window type gem which rubygems go to that folder in windows in windows explorer open up a subfolder rubygems\\ssl_certs copy the AddTrustExternalCARoot-2048.pem to the rubygems\\ssl_certs Retry “gem install sass” –SUCCESS! Installing SASS Build System # The easiest way to install this package is through Package Control.\n1. Download and install the Package Control Plugin. Follow the instructions on the website\n2. Open the command panel: Control+Shift+P (Linux/Windows) or Command+Shift+P (OS X) and select ‘Package Control: Install Package‘.\n3. When the packages list appears type ‘SASS‘ and you’ll find the SASS Build System. Select to install it.\n4. Set the Build System to SASS, In Tools –\u0026gt; Build System –\u0026gt; SASS\n5. Now you can compile your SASS files! Launch your build with Control+B (Linux/Windows) or Command+B (OS X).\n**NOTE** if you are not getting you are not getting colors on your SASS – click the bottom right corner and set the Language as SASS\nStep 2 – Map Network Folder to O365 # The key to this part is to first log into your O365 site w/ Internet Explorer. I then go to any folder and click the button to Open with Explorer. Then I can usually successfully map the network drive.\nPRE-REQUISITES – Your site must be in the trusted sites. I actually add a few *.microsoft[sites] http://blogs.technet.com/b/sharepoint_made_easy/archive/2013/03/20/map-network-drive-webdav-with-sharepoint-online-o365.aspx\nIE \u0026gt; Tools \u0026gt; Internet Options \u0026gt; Security \u0026gt; Trusted Sites [click sites button]\n1. Log into your site with Internet Explorer – be sure to check ‘Keep me signed in’\n2. Open up any document library [might look different. this screenshot from small resolution virtual machine]\n3. Next, do the standard map network drive per your Operating System. I’m running Windows 7.\nbasically right click on Computer and click Map Network Drive\n4. Enter in the site url w/ any subfolder you want [root is fine]. I also check connect using different credentials.\n4.1 Enter in your O365 credentials\n5. SUCCCESS !\nStep 3 – Install \u0026amp; Setup Scout # For this you will need to create a local working folder for all CSS. for example I will create a folder on the desktop called ‘MySassExample’\n1. Download and Install Scout @ http://mhs.github.io/scout-app/\n2. Once Scout installs, Open it up and click the + in the bottom left to add a new project\n3. Navigate to your local working folder\n4. Under Configure, Stylesheet Directories –\u0026gt; set the Input Folder to your local working folder \u0026amp; the Output folder to your network mapped drive (o365)\nI set it to output to the Style Library of my site.\n5. Click the Play Button\nStep 4 – The Final Step, Verifying it all works! # 1. With Scout running, Go to your local working folder.\n2. Create a new .SCSS file or open an existing one in Sublime Text 2\n3. Create some SASS\n4. Save, then CTRL+B to build it. You should get the write ~file output.\n5. In Scout, you should see …. overwrite .css\nSometimes you need to give it a few seconds to save the file to o365, the network drive tends to take longer to save files\n** NOTE if you are using an existing file make sure you check it out via SharePoint. Otherwise it won’t save and you’ll see errors in Scout when it tries **\nWrap Up # The concludes how to setup Scout \u0026amp; Sublime Text 2 to create and update SASS styles sheets which compile to .css and automatically push to O365. The hardest thing I found is mapping the drive successfully. There are many helpful articles on the different errors you might get trying to do so. It may seen like a lot of steps to get going, but if you have used SASS or LESS before you know how powerful it is and how much time you might potentially save. This blog was meant to help get this setup and show you how you can get started. There are other programs like Scout, but Scout was the only one that I could get to work the way I liked. There are also other editors that you could use if you don’t like Sublime Text 2. Best of luck!\nResources # More on Scout – http://www.impressivewebs.com/sass-on-windows-with-scout-app/\nErrors Mapping Network Drive – http://blogs.technet.com/b/sharepoint_made_easy/archive/2013/03/20/map-network-drive-webdav-with-sharepoint-online-o365.aspx\nSASS – http://sass-lang.com/\n","date":"Jan 3, 2015","image":"/images/working-with-sass-style-sheets-in-office-365-sharepoint-2013/working-with-sass-style-sheets-in-office-365-sharepoint-2013-3746b42d_hu_ee1d4bb7ca061fd0.webp","permalink":"/2015/01/03/working-with-sass-style-sheets-in-office-365-sharepoint-2013/","section":"Post","summary":"My new favorite thing is creating all my style sheets with SASS. SASS makes writing my style sheets faster, easier, cleaner, and more reusable. Don’t…","title":"Working with SASS style sheets in Office 365 / SharePoint 2013","type":"posts"},{"content":"Below is some sample code to delete a workflow on the host web from your app web. This would be useful in the event you want your App to remove a workflow that you have deployed to the host web.\nNOTE: you must include a reference to “/_layouts/15/SP.WorkflowServices.js”\nvar deleteWorkflow = function(workflowName) { //Using the App Web as the client context clientContext = new SP.ClientContext.get_current(); //Get the host web URL from the query string params //I have a function getHostWebUrl() - which is not included. //http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript var hostWebUrl = getHostWebUrl(); //Using the hostWebContext as an AppContextSite hostWebContext = new SP.AppContextSite(clientContext, hostWebUrl); //Get the Workflow Services Manager for the Host web, NOTE: you initialize it with the clientContext \u0026amp; the hostWebContext Web) var hostWebWorkflowServicesManager = new SP.WorkflowServices.WorkflowServicesManager.newObject(clientContext, hostWebContext.get_web()); var hostWebWorkflowDeploymentService = hostWebWorkflowServicesManager.getWorkflowDeploymentService(); var hostWebDefinitionsCollection = hostWebWorkflowDeploymentService.enumerateDefinitions(false); //load all the workflow definitions from the host web clientContext.load(hostWebDefinitionsCollection); clientContext.executeQueryAsync(function() { //get the enumerator for all the workflow definitions var workflowDefinitions = hostWebDefinitionsCollection.getEnumerator(); while (workflowDefinitions.moveNext()) { //set the current definition to a variable var workflowDefinition = workflowDefinitions.get_current(); //uncomment to see all the workflows that it finds //console.log(\u0026#34;Found Workflow : \u0026#34; + workflowDefinition.get_displayName()); //looking for a match on the name of the workflow if (workflowDefinition.get_displayName() === workflowName) { //using the host web workflow deployment service to delete the workflow definition hostWebWorkflowDeploymentService.deleteDefinition(workflowDefinition.get_id()); //this comment will just display what workflow WILL BE deleted console.log(\u0026#34;Deleted workflow : \u0026#34; + workflowName + \u0026#34;, \u0026#34; + workflowDefinition.get_id().toString()); //if more than one workflow with the same name is found it will delete all of them //the delete will get queued up and executed by the .executeQueryAsync //if you don\u0026#39;t want that then you have to break out of the while loop } } //this query will execute the delete action clientContext.executeQueryAsync(function() { //Successfully deleted the workflow }, function(sender, args) { console.log(\u0026#34;Unable to delete workflow : \u0026#34; + workflowName); console.log(\u0026#34;Reason : \u0026#34; + args.get_message() + \u0026#34;\u0026#34;); }); }, function(sender, args) { console.log(\u0026#34;Failed to load workflows\u0026#34;); console.log(\u0026#34;Reason : \u0026#34; + args.get_message() + \u0026#34;\u0026#34;); }); }; ","date":"May 14, 2014","image":"/images/delete-a-workflow-on-the-host-web-from-the-app-web/delete-a-workflow-on-the-host-web-from-the-app-web-delete_hu_8ece7d2bac0ce583.webp","permalink":"/2014/05/14/delete-a-workflow-on-the-host-web-from-the-app-web/","section":"Post","summary":"Below is some sample code to delete a workflow on the host web from your app web. This would be useful in the event you…","title":"Delete a Workflow on the Host Web from the App Web","type":"posts"},{"content":"Below is some sample code to provision a workflow on the host web from your app web. This sample code is in one block for demo purposes. It makes 4 asynchronous calls to SharePoint to complete successfully.\nIn the event you’d want to define in your App a workflow that would run on a list in the host web this is the code you would use to deploy (move it from App to Host) and attach it to a list in the host web.\nNOTE: you must include a reference to “/_layouts/15/SP.WorkflowServices.js”\nvar attachWorkflow = function(workflowName, workflowListName, workflowHistoryListName, workflowTasksListName) { //Using the App Web as the client context clientContext = new SP.ClientContext.get_current(); //Get the host web URL from the query string params //I have a function getHostWebUrl() - which is not included. //http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript var hostWebUrl = getHostWebUrl(); //Using the hostWebContext as an AppContextSite hostWebContext = new SP.AppContextSite(clientContext, hostWebUrl); //Get the Workflow Services Manager for the Host web, NOTE: you initialize it with the clientContext \u0026amp; the hostWebContext Web) var hostWebWorkflowServicesManager = new SP.WorkflowServices.WorkflowServicesManager.newObject(clientContext, hostWebContext.get_web()); var hostWebWorkflowDeploymentService = hostWebWorkflowServicesManager.getWorkflowDeploymentService(); var hostWebSubscriptionService = hostWebWorkflowServicesManager.getWorkflowSubscriptionService(); //Get the Workflow Services Manager for the App web var workflowServicesManager = new SP.WorkflowServices.WorkflowServicesManager(clientContext, clientContext.get_web()); var workflowDeploymentService = workflowServicesManager.getWorkflowDeploymentService(); var workflowDeploymentServiceDefinitions = workflowDeploymentService.enumerateDefinitions(false); //Load all the Workflow Definitions from the App web clientContext.load(workflowDeploymentServiceDefinitions); clientContext.executeQueryAsync(function () { //Get the enumerator for all the workflow definitions in the App web var workflowDefinitions = workflowDeploymentServiceDefinitions.getEnumerator(); while (workflowDefinitions.moveNext()) { //set the current definition to a variable var workflowDefinition = workflowDefinitions.get_current(); //test, trying to locate your workflow in the App web if (workflowDefinition.get_displayName() === workflowName) { //now grab the xaml definition of the selected workflow var workflowXaml = workflowDefinition.get_xaml(); //define a new workflow and assign it xaml \u0026amp; name var newWorkflow = new SP.WorkflowServices.WorkflowDefinition.newObject(clientContext, hostWebContext.get_web()); newWorkflow.set_xaml(workflowXaml); newWorkflow.set_displayName(workflowName); //using the host webs Workflow Deployment Service, save the workflow. hostWebWorkflowDeploymentService.saveDefinition(newWorkflow); //load the new workflow and initialize the Id clientContext.load(newWorkflow, \u0026#34;Id\u0026#34;); //this query will save your workflow to the host web clientContext.executeQueryAsync(function() { //publish the workflow on the host web hostWebWorkflowDeploymentService.publishDefinition(newWorkflow.get_id()); //getting all the lists that the workflow will attach to //these lists already created on the host web var targetList = hostWebContext.get_web().get_lists().getByTitle(workflowListName); var historyList = hostWebContext.get_web().get_lists().getByTitle(workflowHistoryListName); var tasksList = hostWebContext.get_web().get_lists().getByTitle(workflowTasksListName); //loading the lists \u0026amp; initialize the Id clientContext.load(targetList, \u0026#34;Id\u0026#34;); clientContext.load(historyList, \u0026#34;Id\u0026#34;); clientContext.load(tasksList, \u0026#34;Id\u0026#34;); //this query will publish the workflow and get the required list id\u0026#39;s for the next step clientContext.executeQueryAsync(function() { //creating a new workflow subscription var subscription = new SP.WorkflowServices.WorkflowSubscription(clientContext, hostWebContext.get_web()); //setting some properties of the subscription subscription.set_name(workflowName + \u0026#34;-ItemAdded\u0026#34;); subscription.set_enabled(true); subscription.set_definitionId(newWorkflow.get_id().toString()); subscription.set_eventSourceId(targetList.get_id()); subscription.set_eventTypes([\u0026#34;ItemAdded\u0026#34;]); subscription.setProperty(\u0026#34;TaskListId\u0026#34;, tasksList.get_id().toString()); subscription.setProperty(\u0026#34;HistoryListId\u0026#34;, historyList.get_id().toString()); subscription.setProperty(\u0026#34;FormData\u0026#34;, \u0026#34;\u0026#34;); //attaching the subscription to the target list hostWebSubscriptionService.publishSubscriptionForList(subscription, targetList.get_id()); //this query will execute the creation of the new subscription and adding attaching the subscription to the target list on the host web clientContext.executeQueryAsync(function() { console.log(\u0026#34;Workflow : \\\u0026#34;\u0026#34; + workflowName + \u0026#34;\\\u0026#34; successfully added to list : \\\u0026#34;\u0026#34; + workflowListName + \u0026#34;\\\u0026#34;\u0026#34;); }, function(sender, args) { console.log(\u0026#34;Workflow : \u0026#34; + workflowName + \u0026#34; not successfully added to list : \u0026#34; + workflowListName); console.log(\u0026#34;Reason : \u0026#34; + args.get_message() + \u0026#34;\u0026#34;); }); }, function(sender, args) { console.log(\u0026#34;Failed to get workflow list IDs\u0026#34;); console.log(\u0026#34;Reason : \u0026#34; + args.get_message() + \u0026#34;\u0026#34;); }); }, function(sender, args) { console.log(\u0026#34;Failed to create the workflow definition\u0026#34;); console.log(\u0026#34;Reason : \u0026#34; + args.get_message() + \u0026#34;\u0026#34;); }); } } }, function (sender, args) { console.log(\u0026#34;Could not find workflow : \u0026#34; + workflowName); console.log(\u0026#34;Reason : \u0026#34; + args.get_message() + \u0026#34;\u0026#34;); }); }; ","date":"May 14, 2014","image":"/images/provision-a-workflow-to-the-host-web-from-the-app-web/provision-a-workflow-to-the-host-web-from-the-app-web-11-01_hu_9cefeb7c0e6c5cab.webp","permalink":"/2014/05/14/provision-a-workflow-to-the-host-web-from-the-app-web/","section":"Post","summary":"Below is some sample code to provision a workflow on the host web from your app web. This sample code is in one block for…","title":"Provision a Workflow to the Host Web from the App Web","type":"posts"},{"content":"Below is some sample code of how to delete a list on the host web from the app web via SharePoint JavaScript object model\nfunction DeleteList(listName) { //Using the App Web as the client context clientContext = new SP.ClientContext.get_current(); //Get the host web URL from the query string params //I have a function getHostWebUrl() - which is not included. //http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript var hostWebUrl = getHostWebUrl(); //Using the hostWebContext as an AppContextSite hostWebContext = new SP.AppContextSite(clientContext, hostWebUrl); //get the list using the host web context var list = hostWebContext.get_web().get_lists().getByTitle(listName); list.deleteObject(); //Always use the context of the app web to do the work or load and executing clientContext.executeQueryAsync(function() { console.log(\u0026#34;Deleted List : \\\u0026#34;\u0026#34; + listName + \u0026#34;\\\u0026#34;\u0026#34;); }, function(sender, args) { console.log(\u0026#34;Failed to delete list : \u0026#34; + listName); console.log(\u0026#34;Reason : \u0026#34; + args.get_message() + \u0026#34;\u0026#34;); }); } // parameters : List Name DeleteList(\u0026#34;My Test List\u0026#34;); ","date":"May 11, 2014","image":"/images/deleting-list-on-the-host-web-from-the-app-web-with-javascript-object-model-jsom/deleting-list-on-the-host-web-from-the-app-web-with-javascri-0cac4315_hu_8ece7d2bac0ce583.webp","permalink":"/2014/05/11/deleting-list-on-the-host-web-from-the-app-web-with-javascript-object-model-jsom/","section":"Post","summary":"Below is some sample code of how to delete a list on the host web from the app web via SharePoint JavaScript object model function…","title":"Deleting a List on the Host Web from the App Web with JavaScript Object Model (JSOM)","type":"posts"},{"content":"Below is some sample code of how to provision a list from the app web via SharePoint JavaScript object model.\nfunction CreateList(title, url, templateType, hidden) { //Using the App Web as the client context clientContext = new SP.ClientContext.get_current(); //Get the host web URL from the query string params //I have a function getHostWebUrl() - which is not included. // Some Ideas Here: http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript var hostWebUrl = getHostWebUrl(); //Using the hostWebContext as an AppContextSite hostWebContext = new SP.AppContextSite(clientContext, hostWebUrl); //Create List Code var listCreation = new SP.ListCreationInformation(); listCreation.set_title(title); listCreation.set_templateType(templateType); listCreation.set_url(url); //must use the hostWebContext to get the list in that site var lists = hostWebContext.get_web().get_lists(); var list = lists.add(listCreation); list.set_hidden(hidden); list.set_onQuickLaunch(false); list.update(); //Always use the context of the app web to do the work or load and executing clientContext.load(list); clientContext.executeQueryAsync(function() { console.log(\u0026#34;Created List : \\\u0026#34;\u0026#34; + title + \u0026#34;\\\u0026#34;\u0026#34;); }, function(sender, args) { console.log(\u0026#34;Failed to create list : \u0026#34; + title); console.log(\u0026#34;Reason : \u0026#34; + args.get_message() + \u0026#34;\u0026#34;); }); } // parameters : List Name, List Url, Template Type (can be ListTemplateType or Template ID, Hidden) CreateList(\u0026#34;My Test List\u0026#34;, \u0026#34;Lists/MyTestList\u0026#34;, SP.ListTemplateType.genericList, false); CreateList(\u0026#34;My Test List\u0026#34;, \u0026#34;Lists/MyTestList\u0026#34;, 171, false); ","date":"May 11, 2014","image":"/images/provisioning-list-from-the-app-web-with-jsom/provisioning-list-from-the-app-web-with-jsom-11-01_hu_9cefeb7c0e6c5cab.webp","permalink":"/2014/05/11/provisioning-list-from-the-app-web-with-jsom/","section":"Post","summary":"Below is some sample code of how to provision a list from the app web via SharePoint JavaScript object model. function CreateList(title, url, templateType, …","title":"Provisioning a List on the Host Web from the App Web with JavaScript Object Model (JSOM)","type":"posts"},{"content":"I recently had to assemble a content query web part (my least favorite thing) pulling data from a Calendar list and experienced hours of pain trying to get it to pull the locations field as well as the start/end time consistently. I share my pain so that other won’t have to endure, and hopefully some tips so that you can skip all the BS and just make a quick web part that should have took about 30 minutes.\nI had an extremely painful time working with the Location field for some reason. It would show on ctrl-f5 or the first load of the web part. Then disappear or show intermittently. It was basically unreliable until I used the ID and renamed the fields as described below.\nGetting the fields right # Step 1 – Calendar list settings # If you go to the list settings and copy or inspect the link to the Start Time field you’ll see something like this.\n/_layouts/15/FldEdit.aspx?List=%7BE84FD6F2%2D11AD%2D443B%2D84A2%2DA6E1B198458D%7D\u0026amp;Field=EventDate\nThis is good to give you what the field name is but we actually just want the field ID. Next I move up to the parent content type and click “Event”\nIn these columns I copy or inspect Start Time field again and you’ll see this\n/_layouts/15/ManageContentTypeField.aspx?ctype=0x010200D536EDB0AB69A047B200ABF60FFE33EF\u0026amp;List=e84fd6f2-11ad-443b-84a2-a6e1b198458d\u0026amp;Field=EventDate\u0026amp;Fid=%7B64cd368d%2D2f95%2D4bfc%2Da1f9%2D8d4324ecb007%7D\nThis is what I want – I’ll get the actual field ID. using a url decoder [http://meyerweb.com/eric/tools/dencoder/]\nFid=%7B64cd368d%2D2f95%2D4bfc%2Da1f9%2D8d4324ecb007%7D\nFid={64cd368d-2f95-4bfc-a1f9-8d4324ecb007}\nStep 2 – Content Query # Once you’ve exported the content query so we can edit it in notepad, search for CommonViewFields\nAdd the Start Time field to this property\n{64cd368d-2f95-4bfc-a1f9-8d4324ecb007},DateTime;\nNext search for DataColumnRenames, add the following. The EventStartDateTime is what you would use in the xslt to pull this property. You can call it what you like at this point.\n{64cd368d-2f95-4bfc-a1f9-8d4324ecb007},EventStartDateTime;\nBelow is a snippet of me using the EventStartDateTime in my xslt. I’m assigning it to a variable to i can further manipulate it.\nStep 3 – Repeat # You can repeat the following steps for a fool proof method of getting the fields you want into your content query web part. If you use the column ID and rename it you should have no issues.\nLocation Field\n{288f5f32-8462-4175-8f09-dd7ba29359a9},Text;\nStart Time\n{64cd368d-2f95-4bfc-a1f9-8d4324ecb007},DateTime;\nEnd Time\n{2684f9f2-54be-429f-ba06-76754fc056bf},DateTime;\n{288f5f32-8462-4175-8f09-dd7ba29359a9},Text;{64cd368d-2f95-4bfc-a1f9-8d4324ecb007},DateTime;{2684f9f2-54be-429f-ba06-76754fc056bf},DateTime;\n{2684f9f2-54be-429f-ba06-76754fc056bf},EventEndDateTime;{288f5f32-8462-4175-8f09-dd7ba29359a9},EventLocation;{64cd368d-2f95-4bfc-a1f9-8d4324ecb007},EventStartDateTime;\nTroubleshooting # From this day forward I vowed to always include this debugging template in my ItemStyle.xsl. It will basically just dump the raw data to your screen, so you can see what fields you have\n= If you set the Item style: DisplayRawData, you’ll get a dump of the fields and values that are available. In the screen shot below you’ll notice then fields that we’ve renamed and they data\n","date":"Apr 1, 2014","image":"/images/content-query-web-part-calendar-mad-problems/content-query-web-part-calendar-mad-problems-Insane-Actors-EMGN2_hu_e67b57e6d5240d38.webp","permalink":"/2014/04/01/content-query-web-part-calendar-mad-problems/","section":"Post","summary":"I recently had to assemble a content query web part (my least favorite thing) pulling data from a Calendar list and experienced hours of pain…","title":"Content Query Web Part + Calendar = Mad Problems","type":"posts"},{"content":"The following updates have been made to this article since it’s publication:\n[Update 9/7/2016] – The method suggested by Håkan Nilsson only works with Publishing Enabled so that you can access the navigation through site setting. You cannot audience the navigation on any site without Publishing Enabled.\n[Update 5/19/2015] – This article is a javascript/css (wsp) based solution.\nA reader Håkan Nilsson has suggested a simple solution to permanently hide the Recent node. Create an empty SharePoint Group In Site Settings -\u0026gt; Look and Feel -\u0026gt; Navigation, Edit the Recent node in Current Navigation Add your blank group to the Audience field. this method is super easy for an end user to implement without deploying a sandbox solution below. But feel free to continue reading if you are interested in that method. Introduction # Yesterday I was tasked to remove the “Recent” node from the quick launch for a customers SharePoint online site. I’ve decided to release the solution in hopes that other people could take advantage of it. The picture below will show you the “Recent” node.\nThis node was undesired by the user because they wanted to have control over the quick launch navigation. They mentioned that every time they add a new list or library then new items will be added under the “Recent” node. When they go and delete it from the Navigation section in Look \u0026amp; Feel, it will just re-appear when they create a new list or library. Basically they wanted it gone.\nApproach # I chose an approach similar to this blog article http://www.jasperoosterveld.com/2013/02/sharepoint-2013-remove-recent-in-quick.html however, I needed it to work on sandbox for o365 and SPO. I also decided to sharpen my JavaScript Client Object model skills and write a small script that will remove the “Recent” node whenever it is created.\nI paired this with the jQuery as suggested in the article to hide the “Recent” node. Except I made one modification to it you can see below. I’ve added the id #sideBox to have this only search in the Quick Launch. I explain more about why I remove and hide it in the next section.\nthis\njQuery(\u0026#34;.ms-core-listMenu-item:contains(\u0026#39;Recent\u0026#39;)\u0026#34;).parent().hide(); to this\njQuery(\u0026#34;#sideBox .ms-core-listMenu-item:contains(\u0026#39;Recent\u0026#39;)\u0026#34;).parent().hide(); The Solution # The entire solution \u0026amp; the pre-built .wsp can be downloaded from CodePlex – http://rr.codeplex.com\nThe sandbox solution is composed of two components:\nRemoveRecent.js – JavaScript Client Object Model code which removes the “Recent” node from the quick launch if detected on page refresh. HideRecent.js – jQuery code to hide the “Recent” node Why remove and hide?\nSince the RemoveRecent.js runs client side there is a case where you may see the “Recent” node. For example you just finished creating a new list and the page refresh. The code to remove the node in RemoveRecent.js is called asynchronously. The list or library is removed from the back end but on the client side is not updated. This could be handled with a refresh but that causes a double refresh once you create a new list and there is a delay between the refreshes. I felt that this created a bad user experience. The answer to that was to also hide the node using HideRecent.js\nHope this may be useful to some of you out there.\n","date":"Jan 8, 2014","image":"","permalink":"/2014/01/08/permanently-removing-the-recent-node-from-quick-launch-for-sharepoint-2013/","section":"Post","summary":"The following updates have been made to this article since it’s publication: [Update 9/7/2016] – The method suggested by Håkan Nilsson only works with …","title":"Permanently Removing the Recent Node from Quick Launch for SharePoint 2013","type":"posts"},{"content":"I know there are quite a few posts out there on this, but i’m writing this here for myself as a rollup of the information and highlighting what needs to be done in order for this to work perfectly.\nIntroduction # For those not familiar with the issue let me re-hash it quickly. When you create a Data View Web Part (DVWP) or New/Display/Edit form in SharePoint Designer you’ll be directly binding those web parts to your list. If you look at the code you’ll see the list GUIDs below.\n\u0026lt;WebPartPages:DataFormWebPart runat=”server” EnableOriginalValue=”False” DisplayName=”My Links” ViewFlag=”8″ ViewContentTypeId=”” Default=”FALSE” ListUrl=”” ListDisplayName=”” ListName=”{62F19BE4-6465-4293-B580-36C49EC8F35B}” ListId=”62f19be4-6465-4293-b580-36c49ec8f35b” PageType=”PAGE_NEWFORM”………………………\nCopying this to another site collection or environment won’t work unless your lists have exactly the same GUID. Basically these web parts are non-reusable. Well there is a way to move them.\nTwo articles that I ALWAYS refer too – but they don’t do it partially or not as cleanly as I want.\nhttp://sympmarc.com/2008/12/16/replacing-listids-with-listnames-in-data-view-web-parts/\nhttp://blogs.msdn.com/b/joshuag/archive/2008/11/10/easily-making-the-data-form-web-part-reusable.aspx\nThere are a few places you need to rename the ListId from in order for this to work.\nThe The The The XSL:StyleSheet Step 1 – DataFormWebPart # This is an original:\n\u0026lt;WebPartPages:DataFormWebPart runat=”server” EnableOriginalValue=”False” DisplayName=”My Links” ViewFlag=”8″ ViewContentTypeId=””\nDefault=”FALSE” ListUrl=”” ListDisplayName=”” ListName=”{62F19BE4-6465-4293-B580-36C49EC8F35B}” ListId=”62f19be4-6465-4293-b580-36c49ec8f35b”\nPageType=”PAGE_NEWFORM” PageSize=”-1″ UseSQLDataSourcePaging=”True” DataSourceID=”” ShowWithSampleData=”False” AsyncRefresh=”False”\nManualRefresh=”False” AutoRefresh=”False” AutoRefreshInterval=”60″ NoDefaultStyle=”TRUE” InitialAsyncDataFetch=”False” Title=”My Links”\nFrameType=”None” SuppressWebPartChrome=”False” Description=”” IsIncluded=”True” PartOrder=”2″ FrameState=”Normal” AllowRemove=”True”\nAllowZoneChange=”True” AllowMinimize=”True” AllowConnect=”True” AllowEdit=”True” AllowHide=”True” IsVisible=”True” DetailLink=”” HelpLink=””\nHelpMode=”Modeless” Dir=”Default” PartImageSmall=”” MissingAssembly=”Cannot import this Web Part.” PartImageLarge=”” IsIncludedFilter=””\nExportControlledProperties=”True” ConnectionID=”00000000-0000-0000-0000-000000000000″ ID=”g_b881fc6c_50db_4108_825a_a5b252da499d”\nChromeType=”None” ExportMode=”All” __MarkupType=”vsattributemarkup” __WebPartId=”{B881FC6C-50DB-4108-825A-A5B252DA499D}” __AllowXSLTEditing=”true”\nWebPart=”true” Height=”” Width=””\u0026gt;\nRemove ListId and ListName completely.\nStep 2 – Data Sources # These are the original data sources. I ALWAYS remove the one’s I’m not using. If your DVWP or List Form does not use the delete then remove it. In my current working example I’m working with an add new item form so all I need it the My updated XML below. I’ve changed the DataFormParameter, Name \u0026amp; ParameterKey to “ListName” and in DefaultValue I’m using the “Links” as the internal name of my list. I’m also specifying the an asp:Parameter of the “WebUrl” so that this can connect to the proper list. This {sitecollectionroot} was pulled from this article – http://blogs.msdn.com/b/joshuag/archive/2008/11/10/easily-making-the-data-form-web-part-reusable.aspx\nStep 3 – Parameter Bindings # Your ParameterBindings should normally look similar to this. I just completely remove the ListId line as it’s not needed in MY xslt\nStep 4 – XSL Style Sheet Params # In the very top of the XSL:StyleSheet you see a list of xsl:params. I just remove the ListID param as I’m not normally using it in my XSL.\n\u0026lt;xsl:stylesheet …………[cut for brevity] ……………………\u0026gt;\n\u0026lt;xsl:output method=”html” indent=”no”/\u0026gt;\n\u0026lt;xsl:decimal-format NaN=””/\u0026gt;\n\u0026lt;xsl:param name=”dvt_apos”\u0026gt;'\u0026lt;/xsl:param\u0026gt;\n\u0026lt;xsl:param name=”ManualRefresh”/\u0026gt;\n\u0026lt;xsl:param name=”ListItemId”\u0026gt;0\u0026lt;/xsl:param\u0026gt;\n\u0026lt;xsl:param name=”ListID”\u0026gt;{62F19BE4-6465-4293-B580-36C49EC8F35B}\u0026lt;/xsl:param\u0026gt;\n\u0026lt;xsl:param name=”MaximumRows”\u0026gt;10\u0026lt;/xsl:param\u0026gt;\n\u0026lt;xsl:param name=”FavoriteLink” /\u0026gt;\n\u0026lt;xsl:variable name=”dvt_1_automode”\u0026gt;0\u0026lt;/xsl:variable\u0026gt;\nFinished…\nEach step of the way you can save and test. When you are finished you should be GUID free.\nConclusion # This may not be a silver bullet for all scenarios of using the DVWP or some of the list form web part through designer. This is what I’ve found to work for me time after time. There may be cases when you need the ListID in the XSLT and you’d want to keep it there and replace the ListID with ListName there.\nIf you find this not to work please contact me and we’ll sort it out and keep this post current.\n","date":"Oct 22, 2013","image":"/images/reuse-dataform-web-part-change-listid-to-listname-and-scrub-guids/reuse-dataform-web-part-change-listid-to-listname-and-scrub--a4d157d8_hu_77b8100c7fc03cc6.webp","permalink":"/2013/10/22/reuse-dataform-web-part-change-listid-to-listname-and-scrub-guids/","section":"Post","summary":"I know there are quite a few posts out there on this, but i’m writing this here for myself as a rollup of the information…","title":"Reuse DataForm Web Part, change ListId to ListName, and scrub GUIDs","type":"posts"},{"content":"Back in December I posted a lengthy article on how to deploy a Composed Look for SharePoint 2013. If you haven’t read that it can be found here – http://tommdaly.wordpress.com/2012/12/19/deploying-a-custom-composed-look-in-sharepoint-2013/. In that article I described the two methods declarative and imperative. Given all the recent buzz around the deprecated sandbox solutions many of our clients are very anti code for their sandbox and online farms/tenants. I’ll keep this post short and sweet but this expands upon the declarative method.\nUpdate [12/16/2016] – a comment from Reego said that using this will replace all the composed looks for O365 and in fact I confirmed this is the case. Ultimately you can get an export of the list and keep all the existing composed looks and add yours at the bottom.\nGoing back to my old post, I provided this excerpt of the elements.xml I used to provision the Composed Look to the Composed Looks list. You might wonder how did I get this, well I made a .wsp via Design Manager, renamed it to .cab, and extracted the files. In there I found the elements.xml that was used to deploy the composed look. Using this I was unhappy because if you look closely below you’ll see some URLs in there. I’m not a fan of using URLs or hard coding ANYTHING. If you’ve been working with SharePoint a long time then you know that this is not always possible in their xml with data views, and provisioning. (yes there are hacks \u0026amp; work arounds but lets not go there)\nSo here is my new snippet of XML for the elements.xml file which is used to create the new list item. I’ve removed the URLs completed and replaced them with ~sitecollection. Alternatively you could use ~site if you just wanted the web URLs to be replaced. No matter what site you upload this to the master page, image, theme, fonts will be connected up properly and no more messy URLs. A few other fields have been cleaned up:\nRemoved HyperlinkBarUrl from Removed Description from Display Order Field simplified Removed FileLeafRef Field Removed ID Field – It will generate the next available, if you set this to 100 then then next available ID becomes 101 when adding another via the UI. download this as file\nI’ve found that deploying this twice it will NOT overwrite itself. So if you make a change you’ll have to delete the Composed Look via the UI and then redeploy. I don’t see any parameters which allow list rows to be overwritten http://msdn.microsoft.com/en-us/library/ms478860.aspx so this may be a limitation. It would be amazing to be able to overwrite itself for any changes.\n","date":"Oct 17, 2013","image":"/images/deploying-composed-look-the-declarative-method-part-2/deploying-composed-look-the-declarative-method-part-2-00_hu_773a9eb1436f0df6.webp","permalink":"/2013/10/17/deploying-composed-look-the-declarative-method-part-2/","section":"Post","summary":"Back in December I posted a lengthy article on how to deploy a Composed Look for SharePoint 2013. If you haven’t read that it can…","title":"Deploying Composed Look – The Declarative Method – Part 2","type":"posts"},{"content":"Recently I was asked to make a free floating navigation element, so that when you scroll down the page it would follow. There are many adaptations of this but for standard html websites, but I was unable to find a SharePoint specific one. Until writing this post I just recently noticed an alternate but similar approach here http://blog.muhimbi.com/2009/08/how-to-float-sharepoint-quick-launch.html\nInstructions # Download the code snippet from here\nAdd the script references to the files in the Master Page, or CEWP.\nOnce you add that to your pages it’s activated. This is tested to work on standard V4.master.\nNotes # If you’re quick launch is larger than the view then it will constantly be scrolling down, and you’ll never see the bottom of the quick launch .\nYour quick launch must be smaller than the actual view.\nI’m using jQuery from the Google hosted API libraries – https://developers.google.com/speed/libraries/devguide#jquery\nHere is a look at the actual script\nDemo # [youtube]JCgv0kkh7PM[/youtube]\n","date":"Aug 18, 2013","image":"","permalink":"/2013/08/18/free-floating-quick-launch-navigation-for-sharepoint-2010-using-jquery/","section":"Post","summary":"Recently I was asked to make a free floating navigation element, so that when you scroll down the page it would follow. There are many…","title":"Free Floating Quick Launch Navigation for SharePoint 2010 using jQuery","type":"posts"},{"content":"Last year I posted a blog article which described the issue with SharePoint 2010 and having a footer. Well similarly SharePoint 2013 has some of the same obstacles when adding a footer. If you are interested in reading more detail into the issue w/ 2010 then go here: My SharePoint 2010 Sticky Footer Solution.\nIt basically boils down to these 2 issues:\nIf you just add a footer to your master page it will be displayed directly under the page content. On a page that has little content then the footer may show half way up the page. Alternatively if you try a pure CSS sticky footer solution it may not work correctly either because SharePoint uses the page height to calculate where the scroll bars will go (mainly due to the header / ribbon always being at the top). [Update 10/9/2013] – Added code from Robert, to handle MDS type pages.\n[Update 9/1/2013] – I noticed that these scripts do not work on Team Sites, this is due to the Minimal Download Strategy Feature that is enabled. Turn that off and this work perfectly fine. I’m still researching how to make the scripts how with that feature enabled.\nI’ve also updated the code to use it’s own function to detect height. Padding (top/bottom) can sometimes throw off the size computations.\nLink the script file # As you can see below I’ve hosted my file in the Style Library\nThe Footer Control # Simply add this div after s4-bodyContainer in the master page file.\n© My Footer Solution 2013 As you can see below an export of the DOM from the IE Dev Toolbar.\nHere below you can see where in the Master Page (based on seattle.master) I look for the id=”DeltaPlaceHolderUtilityContent” and you’ll see 3 above it. I put it right between the 2nd \u0026amp; 3rd The Script File # Download stickyFooter2013.js Here\nThis script is a pure JavaScript solution which will work with a default master page. If you customize it then you’ll need to account for any differences. It basically adds the suite bar + ribbon + body + footer, and if that’s smaller than the viewport aka viewable area. It will expand the body by the difference so that they are equal.\nThis also operates in it’s own namespace so there won’t be any collisions\nI also bind to the window resize event so if the size of the screen changes the ribbon should stay on the bottom.\nEnjoy!\n","date":"Jul 16, 2013","image":"","permalink":"/2013/07/16/my-sticky-footer-solution-for-sharepoint-2013pure-javascript-solution/","section":"Post","summary":"Last year I posted a blog article which described the issue with SharePoint 2010 and having a footer. Well similarly SharePoint 2013 has some of…","title":"My Sticky Footer Solution for SharePoint 2013 – Pure JavaScript Solution","type":"posts"},{"content":"There is a tool released by Microsoft to assist with building SharePoint 2013 Themes more specifically the theme color palettes. This will help a lot when working with the .spcolor files and building custom Composed Looks.\nSharePoint 2013 Theme Builder Tool\n","date":"Jul 12, 2013","image":"","permalink":"/2013/07/12/sharepoint-2013-theme-builder-tool/","section":"Post","summary":"There is a tool released by Microsoft to assist with building SharePoint 2013 Themes more specifically the theme color palettes. This will help a lot…","title":"SharePoint 2013 Theme Builder Tool","type":"posts"},{"content":"This article is based off Thomas Sondergaard’s post located here: http://sharepointsharpener.wordpress.com/2011/09/27/quick-fix-remove-libraries-lists-from-toc/\nThis version is slightly different, I approach it similar but in a different way. That version uses javascript and scans all tags on the page, it did not remove the extra items added from Quick Launch navigation.\nMy version will remove all the lists and libraries as well as any nodes added from the quick launch navigation, and it specifically targets the table of contents web part only.\nDescription # Standard Table of Content webpart will display all Items in the site and any additional headings and links added in the Quick Launch.\nIt pretty much shows everything that’s in the quick launch.\nMy client had the requirement of a site map type web part for SharePoint Online (o365). So I could not program anything and the Table of Content web part was great because we could sort, but they did not want all this ‘other’ stuff, Libraries \u0026amp; Lists \u0026amp; Navigation Headers, appearing in the site map type webpart. They did want it to appear in the quick launch, so I could not remove it from there. The Table of Content is reading directly from the Quick Launch navigation provider so I was stuck.\nSolution # Using jQuery I iterate throught all the headers, then I find which ones are list and libraries.\nLists and Libraries will have an tag which contains ‘BaseType’\nOr I check to see if it’s a standard link and remove that. All the automatically generated one would be relative links. And all the ones added to the quick launch we added as absolute.\nIf the section did not contain a link, then it’s detected as a Header from the quick launch nav provider and I remove that.\nBelow is the script, I load jQuery via a CDN first. Then I’ll add this to the page in a tag. In my actual deployment I added my script to the MasterPage so it would be used site wide.\njQuery(document).ready(function () { var url = window.location.pathname; jQuery(\u0026#34;.toc-layout-main .headermarker\u0026#34;).each(function () { var result = jQuery(this).find(\u0026#34;a\u0026#34;).each(function () { var href = jQuery(this).attr(\u0026#34;href\u0026#34;); if (href.indexOf(\u0026#39;BaseType\u0026#39;) != -1) { var topNode = jQuery(this).parent().parent().parent().parent(); if (topNode.hasClass(\u0026#34;level-section\u0026#34;)) { topNode.hide(); } } else if(href.indexOf(\u0026#39;http\u0026#39;) != -1) { var topNode = jQuery(this).parent().parent().parent(); if (topNode.hasClass(\u0026#34;dfwp-list\u0026#34;)) { topNode.hide(); } } }); if (result.length == 0) { var topNode = jQuery(this).parent().parent().parent(); if (topNode.hasClass(\u0026#34;level-section\u0026#34;)) { topNode.hide(); } } }); }); That’s it. Your final results should look be reduced to only sites and sub sites.\n","date":"Jun 27, 2013","image":"","permalink":"/2013/06/27/customize-table-of-contents-remove-libraries-navigation-headers/","section":"Post","summary":"This article is based off Thomas Sondergaard’s post located here: http://sharepointsharpener.wordpress.com/2011/09/27/quick-fix-remove-libraries-lists-from-toc/ …","title":"Customize Table of Contents Web Part: Remove Libraries \u0026 Navigation Headers","type":"posts"},{"content":" What is cache busting? # Cache busting is way to ensure that the browser will download a new version of your file. This is easily accomplished using a simple technique of appending a ? with some unique characters at the end.\nex:\nThe browser sees this as a different file and will then download and cache this new file. Each time the browser see’s a different string it will download the file and cache it. Some methods use a revision number, a date, random number, or a signature as a query string parameter. Taking a look at the example above you will see a version number is specified at the end of the .css file to do the cache busting.\nSharePoint 2010 uses a technique which involved computing a MD5 hash signature of the file and appending a ?rev={MD5HASH} to the link. This technique works very well because the computed hash won’t change unless you change something in the file. Also the computed MD5 hash will be unique to ensure that the file will appear new to the browser. For all you super technical people, yes MD5 is not guaranteed to be unique, there is a very very small chance of the hash being the same. This technique of the computed MD5 hash signature is used for both CssRegistration and ScriptLink in SharePoint 2010. If you are interested in more detail then reflect into Microsoft.SharePoint.dll –\u0026gt; Microsoft.SharePoint.Utilities –\u0026gt; SPUtility –\u0026gt; MakeBrowserCacheSafeLayoutsUrl\nCache busting for CssLink # When using CssRegistration the files need to be relative to the Layouts folder, more specifically LAYOUTS\\1033\\STYLES\\{CssFile}.\nIf you do not keep the files in the LAYOUTS\\1033\\STYLES then you will receive an error\nBelow is the rendered html output of the CssRegistration tag above\nYou can see that “/_layouts/1033/styles/” is prepended to the ‘Name’ property provided in the CssRegistration tag and your .css file is successfully cache safe.\nCache busting for ScriptLink # When using ScriptLink the files need to be relative to the Layouts folder. Unlike the CssRegistration, ScriptLink has a property called ‘Localizable’. In the asp tag below I specify Localizable=”False”\nIf you don’t specify Localizable=”False” then you need to put the files relative to the Layouts\\1033 folder.\nBelow is the rendered html output of the ScriptLink tag above\nYou can see that the “/_layouts/” is prepended to the ‘Name’ property provided in the ScriptLink tag and your .js file is successfully cache safe.\nWhat if I am deploying files to the Style Library? # In the scenario where you are deploy CSS to the Site Collection style library then there is no automated way of using the SharePoint cache busting from the CssLink, however you can take a look at Chris O’Brien’s posting – Avoiding bugs from cached JavaScript and CSS files in SharePoint. In this posting he has a solution in which he creates a new class inheriting from CssLink and adds his own mechanism of cache busting. He also mentions that there is no equivalent way to do the same for JavaScript files stored in the content db.\nSummary # SharePoint 2010 provides an easy way to do cache busting on CSS and JS files stored in the LAYOUTS folder.\nIf you store your files in the ‘Style Library’ or content DB then you have to revert to manual methods or get creative with your own solutions.\n","date":"Jan 20, 2013","image":"","permalink":"/2013/01/20/cache-busting-using-cssregistration-scriptlink-in-sharepoint-2010/","section":"Post","summary":"What is cache busting? Cache busting is way to ensure that the browser will download a new version of your file. This is easily accomplished…","title":"Cache Busting Using CssRegistration \u0026 ScriptLink in SharePoint 2010","type":"posts"},{"content":"Applying a theme has change from SP2010 to SP2013, here is a link to the method to apply a composed look.\nSPWeb.ApplyTheme( string colorPaletteUrl, string fontSchemeUrl, string backgroundImageUrl, bool shareGenerated) http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spweb.applytheme.aspx\n","date":"Dec 19, 2012","image":"","permalink":"/2012/12/19/applying-a-composed-look-programmatically/","section":"Post","summary":"Applying a theme has change from SP2010 to SP2013, here is a link to the method to apply a composed look. SPWeb.ApplyTheme( string colorPaletteUrl, string…","title":"Applying a Composed Look programmatically","type":"posts"},{"content":"There are two ways to deploy your custom composed look. Via XML / Elements file – the declarative way, or via feature receiver / C# the imperative way.\nThis article will cover both methods, it’s up to you to ultimately decide which way you prefer.\nFirst I will explain how to put together the the Visual Studio 2012 project with the assets for the composed look. If you already know how to deploy these assets or they already exist in SharePoint skip down to the bottom.\nThe Project # Starting with an empty SharePoint Project – farm solution.\nFirst we need to add 4 things which are used by the Composed Look.\nMaster Page Theme (Colors) Font Scheme (Optional) Background Image (Optional) Adding the Master Page # In the Visual Studio Project, right click the solution, select Add, select New Item\nSelect Module –\u0026gt; Name: MasterPage\nIn the MasterPage module, Delete the sample.txt\nCopy in your masterpage\nEdit the elements file, make it look like this.\nThis completes this section.\nAdding the Theme # In the Visual Studio Project, right click the solution, select Add, select New Item\nSelect Module –\u0026gt; Name: Theme\nIn the Theme module, Delete the sample.txt\nCopy in your {theme}.spcolor\nEdit the elements file, make it look like this.\nThis completes this section.\nAdding the Font Scheme # In the Theme module, add in your {fontScheme}.spfont\nEdit the elements file, make it look like this\n\u0026lt;File Path=”Theme\\MyFontScheme.spfont” Url=”MyFontScheme.spfont”\nType=”GhostableInLibrary” IgnoreIfAlreadyExists=”TRUE” /\u0026gt;\nThis completes this section.\nAdding the Background Image # In the Visual Studio Project, right click the solution, select Add, select New Item\nSelect, SharePoint “Images” Mapped Folder [Alternatively you can deploy this to the layouts]\nAdd your background image to that folder\nThis completes this section.\nAdding a Composed Look – the Declarative Method # In the Visual Studio Project, right click the solution, select Add, select New Item\nSelect Empty Element –\u0026gt; Name: MyComposedLook\nEdit the elements file\nMake sure to match up the MasterPageUrl, ThemeUrl, ImageUrl, FontSchemeUrl.\n0x0060A82B9F5D2F6A44A6A5723277B06731 100 My New Composed Look 0 0 2_.000 My New Composed Look http://intranet/_catalogs/masterpage/MyMasterPage.master, /_catalogs/masterpage/MyMasterPage.master http://intranet/_catalogs/theme/15/MyTheme.spcolor, /_catalogs/theme/15/MyTheme.spcolor http://intranet/_layouts/15/images/MyComposedLook/bg.gif, /_layouts/15/MyComposedLook/images/bg.gif http://intranet/_catalogs/theme/15/MyFontScheme.spfont, /_catalogs/theme/15/MyFontScheme.spfont 1.0000000000000 This completes this section.\nAdding a Composed Look – the Imperative Method # In the Visual Studio Project, right click the feature, select Add Event Receiver\nInside the FeatureActivated add the following\n**This is experimental code, make sure to do your own testing if using**\nconst string CustomLookName = \u0026#34;MyComposedLook\u0026#34;; const string MasterPageUrl = \u0026#34;_catalogs/masterpage/MyMasterPage.master\u0026#34;; const string ThemeUrl = \u0026#34;_catalogs/theme/15/MyTheme.spcolor\u0026#34;; const string FontSchemeUrl = \u0026#34;_catalogs/theme/15/MyFontScheme.spfont\u0026#34;; const string ImageUrl = \u0026#34;_layouts/15/images/MyComposedLook/bg.gif\u0026#34;; var site = properties.Feature.Parent as SPSite; if (site != null) { var serverRelativeUrl = site.RootWeb.ServerRelativeUrl; SPList list = site.RootWeb.Lists[\u0026#34;Composed Looks\u0026#34;]; bool match = false; foreach (SPListItem listItem in list.Items) { if (listItem.Name == CustomLookName) { match = true; } } if (!match) { SPListItem item = list.AddItem(); item[\u0026#34;Title\u0026#34;] = CustomLookName; item[\u0026#34;Name\u0026#34;] = CustomLookName; SPFieldUrlValue masterUrl = new SPFieldUrlValue(); masterUrl.Url = serverRelativeUrl + MasterPageUrl; masterUrl.Description = serverRelativeUrl + MasterPageUrl; item[\u0026#34;MasterPageUrl\u0026#34;] = masterUrl; SPFieldUrlValue themeUrl = new SPFieldUrlValue(); themeUrl.Url = serverRelativeUrl + ThemeUrl; themeUrl.Description = serverRelativeUrl + ThemeUrl; item[\u0026#34;ThemeUrl\u0026#34;] = themeUrl; SPFieldUrlValue imageUrl = new SPFieldUrlValue(); imageUrl.Url = \u0026#34;\u0026#34;; imageUrl.Description = \u0026#34;\u0026#34;; item[\u0026#34;ImageUrl\u0026#34;] = imageUrl; SPFieldUrlValue fontSchemeUrl = new SPFieldUrlValue(); fontSchemeUrl.Url = \u0026#34;\u0026#34;; fontSchemeUrl.Description = \u0026#34;\u0026#34;; item[\u0026#34;FontSchemeUrl\u0026#34;] = fontSchemeUrl; item[\u0026#34;DisplayOrder\u0026#34;] = 100; item.Update(); } site.RootWeb.ApplyTheme(serverRelativeUrl + ThemeUrl, null, null, true); } Adding the Modules/Elements to the Feature # Double click on your feature, and ensure that the MasterPage \u0026amp; Theme are included in the feature.\nIf your using the declarative method for the Composed Look then make sure the MyComposedLook Element is in the feature.\nIf your using the imperative method then your feature receiver will do the work when activated.\nMake sure that the Scope is set to Site\nThis completes this section.\nDeploy and enjoy\n","date":"Dec 19, 2012","image":"","permalink":"/2012/12/19/deploying-a-custom-composed-look-in-sharepoint-2013/","section":"Post","summary":"There are two ways to deploy your custom composed look. Via XML / Elements file – the declarative way, or via feature receiver / C#…","title":"Deploying a custom ‘Composed Look’ in SharePoint 2013","type":"posts"},{"content":"I’ve been experimenting heavily lately with SharePoint 2013 and the entire process wrapped around Branding sites. I am one of those people that rely on working on the actual SharePoint server because I do branding and development.\nDesign Manager\nDesign Manager is the process of uploading your design files to your site. As part of this process when you get to step #3 it will tell you to map a network folder to easily upload your design assets to the Master Pages gallery.\nTo map my folder I use the normal process of doing such. Something like this –\u0026gt; http://support.microsoft.com/kb/308582\nDoing this caused an error on my basic Windows 2008 R2 server.\nError – Windows cannot access http://\nError Code: 0x80070043\nThe network name cannot be found.\nHow to Fix # Add the feature ‘Desktop Experience’ to your server\nBy going to server manager and adding this feature, upon reboot I was able to successfully map the network drive to my SharePoint 2013 master pages gallery\n","date":"Nov 7, 2012","image":"","permalink":"/2012/11/07/sharepoint-2013-design-manager-unable-to-map-network-drive-while-working-on-the-server/","section":"Post","summary":"I’ve been experimenting heavily lately with SharePoint 2013 and the entire process wrapped around Branding sites. I am one of those people that rely on…","title":"SharePoint 2013 – Design Manager – Unable to map network drive, while working on the server","type":"posts"},{"content":"For those working with SharePoint 2010 themes this tools seemed to be amazing at giving me those .THMX files. Until they took it away, but they never told me it would come back as something else.\nIf you go to http://connect.microsoft.com/ThemeBuilder and sign in with you window live id you’ll notice a nice error message\nTheme Builder has been reincarnated and I just found it yesterday. I’ve always kept a copy of the original Theme Builder anyway. But now here it is\nOpen XML Theme Builder # ","date":"Sep 27, 2012","image":"","permalink":"/2012/09/27/theme-builder-lives-on/","section":"Post","summary":"For those working with SharePoint 2010 themes this tools seemed to be amazing at giving me those .THMX files. Until they took it away, but…","title":"Theme Builder Lives On!","type":"posts"},{"content":"I’m compiling a list of my favorite XSL tip and tricks that always help me. These items are not going to be in any particular order\n1) Linking Javascript or CSS # ]]\u0026gt; 2) Stripping HTML # the first section is the how to call the template below it\n3) Trim String # ... 4) Show all XML output # \u0026lt;xsl:copy-of select=\u0026#34;*\u0026#34;/\u0026gt; 5) SharePoint Templates # A nice set of pre-built .xsl templates for use which contains some helpful things.\nSharePoint XSL Templates\n","date":"Sep 16, 2012","image":"","permalink":"/2012/09/16/add-css-or-scripts-to-xsl/","section":"Post","summary":"I’m compiling a list of my favorite XSL tip and tricks that always help me. These items are not going to be in any particular…","title":"My favorite XSL tips / tricks","type":"posts"},{"content":"I must say that I’ve been using for my style sheets for some times now. Until recently have seen the benefits from using the CSSRegstration method and now using that exclusively from now on. Even in my master page where I know the CSS files will only load once I will use it. I was unaware that CSSRegistration had Conditional Expression built in to target other browsers, but now that I know that I have no excuse not to use it.\nUsing the After property # Most of the time you’ll want to load your CSS file after the core V4 css gets loaded. To do this you’ll use the property After=”corev4″.css”\nYou’ll see in image below that the files are appearing after the corev4.css file which is the main SharePoint styling CSS file.\nRemoving the After property you’ll see the style0.css applied before the corve4.css and style1.css applied after it.\nOrder of Loading # Take notice to the order that you are setting them in the master page. The style sheets set first in the master page will be applied last on page load.\nWhen the page loads you can see the output.\nStyle1.css , then Style0.css\nUsing Conditional CSS # The CSSRegstration class has properties to target specific browsers similar to this method. http://www.quirksmode.org/css/condcom.html\nWhich renders like this, where the comments will target different browsers.\nYou don’t have to enter the IF in the condition, that is done for you. But to access NON IE browsers you’ll have to specify another property to reach downlevel clients. Pay special attention to the ConditionalExpression which is set to !IE, to reach other browsers you need to also specify the RevealToNonIE=”True”\nDoing so will prepare that conditional CSS comments in a way that Firefox, Chrome or other Non-IE browsers can read them.\nControlling Multiple Loading # On benefit of using the SharePoint:CSSRegistration tag is to handle the issue of loading a CSS file multiple times. As CSS files grow bigger and bigger it can be a problem with page load times if you are loading multiple web parts and using the link tag\nBy using the regular tag to reference your CSS files each time that gets hit it will load your css to the page.\nUsing CSS Registration will not do that. take this example below. I’m attempting to register the Style1.css, 2 additional times, and style0.css 1 additional time\nand the output will only load the css files once\nSpecifying Current Site Collection Urls # Another benefit of using the SharePoint:CSSRegistration tag is the ability to specify files that live in the Style Library in a Site Collection not located at the root of the domain. What do I mean by that? Consider that we are building a branding solution and we are storing the files in the Style Library of that site collection and say for example that the domain is test.contoso.com\nSo deploying our solution to test.contoso.com would put the files in at test.contoso.com/Style Library/mycss.css. Now when we register the CSS files using the tag it would look like this\nSay you have sub site collections under another managed path that link would break. For example, consider another site collection called /Sites/SC1. This would be test.contoso.com/sites/sc1 and the masterpage would try to find the file at test.contoso.com/Style Library using that tag above.\nThat would NORMALLY not be a problem if the branding solution or files were deployed to the site collection at “/” and then at “/sites/sc1”. BUT if you only deploy the branding to the “/site/sc1” site collection the files would only exist in that Style Library for that site collection. So the links to the css would be looking at test.contoso.com/Style Library/mycss.css and be broken because the files are NOT there and the files are at test.contoso.com/sites/sc1/Style Library/SC1.\nSo as confusing as that may have sounded there is an answer to handle that situation.\n1. You deploy CSS and images to the Layouts folder – This makes the files available globally. Any reference to CSS or images should start with /_layouts/MyDeploymentFolder/myCss.css\n2. Use SPUrl to handle the adjustment of the link to the appropriate site collection.\nExamining #2 – SPUrl # By using the $SPUrl:~SiteCollection you can specify a parameter which will return the current site collection. Below I’ve embedded into a master page\nThis is the result on my root site collection “/”\nThis is the result on another site collection under a managed path “/sites/sc1”\nSo as you can see, using this method you can deploy your css / images to the Style Library and be able to access them using this current site collection url parameter.\nThis about wraps up the major aspects of using CSSRegistration over Link. To sum up the main benefits we have:\nAchieved with Link or CSS Registration\nControlling load order Conditional CSS Specify site collection relative style sheets Achieved with CSS Registration only\nPreventing loading the same file multiple times ","date":"May 2, 2012","image":"","permalink":"/2012/05/02/sharepoint-cssregistration-or-link/","section":"Post","summary":"I must say that I’ve been using for my style sheets for some times now. Until recently have seen the benefits from using the…","title":"SharePoint CSSRegistration or Link?","type":"posts"},{"content":"In this post we are going to create a 2 tier navigation menu using SPServices. In this scenario we’ll be replacing the current quick launch and putting in our new navigation. This will work with whatever version of SharePoint.\nThis solution would most likely work with more tiers but the CSS I have provided in the example only accounts for 2 tiers\nThings you’ll need\njQuery\nSPServices\na Data Source (SharePoint List) – I have specifically configured my list EXACTLY as defined in this way.\nOn to the implementation steps\nPrior to starting any front end work make sure you have the Custom List as defined above created. I’ve called my list ‘Navigation’\nCreate a few nodes so that you’ll have some data and know that its working properly.\nAfter creating the list and some test nodes, make sure that you have resources to the jQuery and SPServices on your site.\nI am storing those files on my SiteCollection / js Folder which I created in SharePoint Designer. You can potentially store the files anywhere or get jquery through a Content Delivery Network (CDN) – http://docs.jquery.com/Downloading_jQuery#CDN_Hosted_jQuery\nand just recently SPServices is now available through a CDN -http://www.cdnjs.com/#/search/jquery.spservices\nI’ve just downloaded the files from the site and I’ll be hosting them in a folder on my Site Collection as I mentioned earlier. Here is a picture of them in their location.\nYou might have noticed in that picture the navigation.js. We need to create a blank file as this point called navigation.js this is where the navigation jQuery code will go. You can download that file here.\nOnce you have that file open it up in a text editor. In the first line set the assignment of the variable “NavigationList” equal to the name of the new list you created. If you are querying a list on another site you would need to specify the WebURL, otherwise leave it blank and it will query the site that the web part is currently on.\nOnce your are done editing save the list alongside the other .js files.\n**NOTE: If you see line 6, this is the object that we will be replacing. You can play around and modify that to potentially go wherever. **\nNext I start off with a generic SharePoint page, and I’ll add a Content Editor Web Part (CEWP). I start off this way because the Content Editor is the easiest way to demo this example. You could embed this into a portion of the master page or some other method.\nEdit the CEWP properties, click on Source Editor\nAdd the following information, take special notice to the paths to the files. In this demo I’m on the /sites/tew , site collection. Adjust that to meet your correct path.\nSo above we are linking to a CSS file that we have not yet created (but will). The first JavaScript link is to the jQuery library. The second is to SPServices library. The third to our custom code to create the navigation.\nSo Save that content in there, hit OK or Save to commit the page.\nNow lets add the CSS file. A sample CSS file can be provided here, or you can create your own.\nOnce you have the correct JS files in place and the CSS in place when you browse or refresh the page you should see the new navigation instead of the quick launch.\nHere is a before and after shot.\nMy files for download again are\nThe Navigation Script\nThe Navigation CSS\n","date":"Apr 26, 2012","image":"","permalink":"/2012/04/26/building-a-custom-navigation-menu-using-spservices/","section":"Post","summary":"In this post we are going to create a 2 tier navigation menu using SPServices. In this scenario we’ll be replacing the current quick launch…","title":"Building a custom Navigation Menu using SPServices","type":"posts"},{"content":" Problem # I’ve come across a handful of sticky footer solutions and many of them claiming to work in SharePoint. Most of the CSS ones just can’t cut it b/c of how SharePoint 2010 now adjusts the page height for the scrollbar / Ribbon. It just never seems to work. A few solutions that do work involve jQuery, now I don’t like this b/c now you need to add the reference to the jQuery library. This is just another resource that if not needed I’d rather not add. My solution was written in JavaScript so that no additional resources are needed. Just attach the JavaScript file and adjust it as I’ll demonstrate to get it to work on your site.\nFor the impatient: Download script here , Read Instructions under the Solution header\nCurrent Issue # So every time I do a new master page with a footer, I am stuck with a page that has no content and the footer is floating way above the bottom of the page like so.\nThis is using the OOTB V4.master (a copy of course), and my footer placed right above the Developer Dashboard so it’s the last thing inside the #s4-bodyContainer.\n(Style put inline for demonstration purposes)\nEven if you go outside the #s4-bodyContainer and place the footer, as the last element in s4-workspace you’ll get the same effect. (I’ve added comments this time to show where I am in the master page)\nIf you go below the s4-workspace, you will get a very undesirable effect, because SharePoint only detects things inside the workspace and uses that to resize the page. SO you’ll see your footer for a second then it will be pushed out of frame!\nSolution # Again as stated in the problem statement, many of the solutions you’ll find rely on jQuery. This one is in JavaScript so that you don’t have to worry about loading that jQuery library. This solution will analyze the size of the view space and adjust the body container taking in considers of the height of the footer, the ribbon \u0026amp; itself.\nStep 1) To utilize this script the placement of your footer must be between the s4-bodyContainer \u0026amp; s4-workspace.\n_(Comments were added by my and will not be there on the default master page)\n_\nStep 2 ) Download the JavaScript file here.\nStep 3 ) Add the js file to your SharePoint site\nStep 4 ) Add the reference in the Master Page, somewhere before the tag\nOnce you complete all the steps your footer will now be properly placed at the bottom of a short page. It will even update itself when the page is stretched or shrunk.\n","date":"Feb 1, 2012","image":"","permalink":"/2012/02/01/my-sharepoint-2010-sticky-footer-solution-2/","section":"Post","summary":"Problem I’ve come across a handful of sticky footer solutions and many of them claiming to work in SharePoint. Most of the CSS ones just…","title":"My SharePoint 2010 Sticky Footer Solution","type":"posts"},{"content":"I was recently asked to get the navigation to work on a tablet. Well part of the problem is the there is really no such thing as a Hover on those devices. Once you touch the screen it’s considered a click.\nAfter browsing around the internet I found this article http://blog.0100.tv/2010/05/fixing-the-hover-event-on-the-ipadiphoneipod/ which contains somewhat of a fix for navigation menus. It basically explained to me that I can attach to a ‘touchstart’ and ‘touchend’ event. Expanding on that idea, I created a small jQuery snippet which works for the SharePoint OOTB navigation which will allow the user to use the navigation on their IPad, IPhone, or IPod. I will be working on support for other touchscreen devices in the near future.\nHeres the code.. make sure you have the base jQuery library on your page.\n$(document).ready(function() { if ((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) || (navigator.userAgent.match(/iPad/i))) { $(\u0026#34;.s4-tn .menu-horizontal ul.root li.dynamic-children a.dynamic-children\u0026#34;).bind(\u0026#39;touchstart\u0026#39;, function(e) { var position = $(this).next().position(); if (eval(position.left \u0026lt; -1)) { $(this).click(function(c) { c.preventDefault(); }); } else { var location = $(this).attr(\u0026#34;href\u0026#34;); window.location = location; } }); } }); This will allow you to touch once to open a menu item that has children. Touching that menu item again would navigate to that node.\nThis has worked pretty well for me. If you decide to try it out let me know if you encounter any issues.\n","date":"Nov 10, 2011","image":"","permalink":"/2011/11/10/ootb-navigation-fix-to-support-ipad-i-devices/","section":"Post","summary":"I was recently asked to get the navigation to work on a tablet. Well part of the problem is the there is really no such…","title":"OOTB Navigation fix to support IPad \u0026 I-Devices","type":"posts"},{"content":" Intro # This goes out to those people that want to use the managed meta data navigation / tree view navigation and are using the starter master page or another simplified master page. By default the tree view navigation is hidden in the starter master page so you’ll need to put that back. The other issue that some people may have encountered is that the vertical navigation resizer is nowhere to be found. The following sections will explain the issues and how to restore the navigations horizontal resizing functionality. I am a big proponent of the starter.masterpage. I use it frequently and just recently noticed this when comparing the meta data navigation.\nTree View Navigation \u0026amp; Managed Meta Data Navigation # You can add back in the Tree View Control which is used to show the Tree View Navigation and the Meta Data Navigation. Just add this control back from the hidden zone in the Starter master page. You’ll want to paste it back in the div id=’s4-leftpanel’ somewhere around line 459.\nHidden Control @ Bottom of Starter Master Page -\u0026gt; \u0026lt;SharePoint:DelegateControl runat=”server” ControlId=”TreeViewAndDataSource”\u0026gt;\u0026lt;/SharePoint:DelegateControl\u0026gt;\n_Left image displays no tree view navigation on the starter master page. Right image displays tree view navigation on v4.master.\n_\nTree View Navigation – Lost Horizontal Expansion (aka GrippyVerticalBarPositionHelper) # Because of the simplified structure of the starter master page you will lose the vertical grip which can be useful in dynamically resizing the width of the left quick launch. You CAN get this back but you need to re-structure your master page.\n_Vertical Grip as seen on V4.master\n_\nThis vertical grip / horizontal resizer goes away in the starter master page because its missing one of the key div’s which make it possible.\nThe following div needs to surround the div id=’MSO_ContentDiv’.\n[clipped for brevity] Adding this around the ‘MSO_ContentDiv’ on the starter master page and you will get this. (This assumes that the tree view control was previously added)\nWhat you might not immediately notice is that there is a huge left side margin. This is because the structure of the HTML in the starter has been reduced that this is still not going to display correctly. The correct minimal structure would be:\n[clipped for brevity] [clipped for brevity] As you can see from the markup above, the ‘s4-leftpanel’ and the ‘MSO_ContentTable’ are sibling nodes. So the reason why this does not work in the starter master page is because the ‘s4-leftpanel’ node is actually inside the ‘MSO_ContentDiv’, plus there is no ‘MSO_ContentTable’ which needs to be present in order for the vertical navigation resizer to appear.\n","date":"Nov 1, 2011","image":"","permalink":"/2011/11/01/tree-view-navigation-managed-meta-data-navigation-missing-vertical-grip-in-starter-masterpage/","section":"Post","summary":"Intro This goes out to those people that want to use the managed meta data navigation / tree view navigation and are using the starter…","title":"Tree View Navigation \u0026 Managed Meta Data Navigation, Missing Vertical Grip in starter.masterpage","type":"posts"},{"content":"Following this guide you’ll see the process that goes behind creating the feature which commonly be used to deploy branding items such as Master Pages, CSS, JavaScript, \u0026amp; Themes. If you plan to follow along just replace the steps with my name with whatever you wish.\nConfigure the Project # Open Visual Studio 2010 File -\u0026gt; New -\u0026gt; Project Select SharePoint -\u0026gt; Empty SharePoint Project Add Name: TomDaly.SharePoint.Branding (this is my standard structure) Add Location: C:\\Projects\\TomDaly Click OK Enter your test site for debugging purposes. Select ‘Deploy as farm solution’ Click Finish Adding the CSS / Image / JavaScript Files # Add CSS file # Right Click on the Project -\u0026gt; Add -\u0026gt; SharePoint ‘Layouts’ Mapped Folder This created a folder which is mapped to the layouts ** These files in here are accessible through the web via http://YOURSITE/_layouts/TomDaly.SharePoint.Branding/\nRight click on Layouts\\TomDaly.SharePoint.Branding folder -\u0026gt; Add -\u0026gt; New Item Select Web on the left, and Style Sheet on the right, and Name the file at the bottom. (I typically use base.css or style.css for the foundation of my styles, but this is all up to you)\nClick Add Add Images \u0026amp; JavaScript Folder # This would be the location for any images associated with the site css.\nRight Click on the Layouts\\TomDaly.SharePoint.Branding folder -\u0026gt; Add -\u0026gt; New Folder Name the folder ‘images’ OPTIONAL STEP\nRepeat Step 1 \u0026amp; 2 for the JavaScript folder. (I typically call this folder ‘js’) I usually include a JavaScript folder because most of the time I end up using jQuery somewhere on the site, this is where I store those files which I would reference on page or in the Master Page. Depending on how it’s needed.\nThese files would be accessible: http://YOURSITE /_layouts/TomDaly.SharePoint.Branding/js/JSFILESHERE\nSetting up the Feature # In your project Right click on Features -\u0026gt; Add Feature This will create a feature with some default name of ‘Feature 1’, which we don’t want\nSelect Feature1, Right Click -\u0026gt; Rename Rename the Feature to TomDaly.SharePoint.Branding (I typically name it to the same as the project name) Double Click on your Primary feature, in the main left hand window the properties should appear Give your feature a normal title name Title and Description, and scope it accordingly. (I usually scope my master pages to (Site) as they are normally used through the site collection Click Save Renaming the WSP # This is really annoying to me so I always change it. When a .WSP is generated it will usually come out as Feature_Feature.wsp. I prefer just Feature.wsp.\n1. Double Click on the primary Feature, the Properties should appear right below it.\n2. Change ‘Deployment Path’\nFrom: $SharePoint.Project.FileNameWithoutExtension$_$SharePoint.Feature.FileNameWithoutExtension$\nTo: $SharePoint.Project.FileNameWithoutExtension$\nAdding the Master Page # 1. Right Click on the Project -\u0026gt; Add -\u0026gt; New Item\n2. In the left Installed Template column, Select SharePoint 2010\n3. On the right select Module\n4. At the bottom name the Module, Master Pages\n5. Click Add\n** You module will be added to your project **\n6. Under the Master Pages Module, right click on Sample.txt and Delete It\n7. Now Drag \u0026amp; Drag and Drop your Custom Master Page from another folder into the project, In the Master Pages Module\n8. Double Click on the Elements.xml in the Master Pages Module\n9. In the Elements.xml file, make the following changes to the line\nChange\n\u0026lt;Module\nName=“MasterPages“\u0026gt;\nTo\n\u0026lt;Module\nName=“MasterPages“\nUrl=“_catalogs/masterpage“\u0026gt;\n10. In the Elements.xml file, make the following changes to the line\na. add IgnoreIfAlreadyExists=“True“\nb. add Type=“GhostableInLibrary“\nc. remove MasterPages/ from the\nUrl=“MasterPages/TOMDALY.master“\nSo essentially this line\n\u0026lt;File\nPath=“MasterPages\\TOMDALY.master“\nUrl=“MasterPages/TOMDALY.master“ /\u0026gt;\nChanges to\n\u0026lt;File\nPath=“MasterPages\\TOMDALY.master“\nUrl=“TOMDALY.master“\nIgnoreIfAlreadyExists=“True“\nType=“GhostableInLibrary“ /\u0026gt;\n11. Click Save\nAdding Theme # This step will incorporate a theme into your project. How to generate the .thmx is not covered here but a simple way would be to export from PowerPoint or use ThemeBuilder to generate this file.\n1. Right Click on the Project -\u0026gt; Add -\u0026gt; New Item\n2. On the left Click SharePoint -\u0026gt; 2010\n3. On the Right Select “Module”\n4. Add Name: Theme\n5. Click Add\n6. In the Theme node, Delete Sample.Txt\n7. Copy in you custom .thmx file\n8. Double Click on the Elements.xml in the Theme Module\n9. In the Elements.xml file, make the following changes to the line\nChange\n\u0026lt;Module\nName=“Theme“\u0026gt;\nTo\n\u0026lt;Module\nName=“Theme“\nUrl=“_catalogs/theme“\u0026gt;\n10. In the Elements.xml file, make the following changes to the line\na. add IgnoreIfAlreadyExists=“True“\nb. add Type=“GhostableInLibrary“\nc. remove Theme/ from the\nUrl=“Theme/TomDaly.thmx“\nSo essentially this line\n\u0026lt;File\nPath=“Theme\\TomDaly.thmx“\nUrl=“Theme/TomDaly.thmx“ /\u0026gt;\nChanges to\n\u0026lt;File\nPath=“Theme\\TomDaly.thmx\n” Url=“TomDaly.thmx“\nIgnoreIfAlreadyExists=“True“\nType=“GhostableInLibrary“ /\u0026gt;\n11. Click Save\nAdding the Feature Receiver # This whole step is optional. Its sole purpose is to automatically turn on the branding on the sites and subsites, apply a theme, or apply search master pages to your site collection. NOTE: If you changed the scope then there is no guarantee that this code will work.\nPlease take some time to look through the code as there are different sections you might want to comment out or fill in. Say if you want to configure the Site Logo or an Alternative CSS file. If you not interested and just want a working project jump down to the END and in the summary there is a link to download the project.\n1. Right Click your primary Feature, Select Add Event Receiver\n** This will get added Right under your feature**\n2. Double Click on the new file TomDaly.SharePoint.EventReciever.cs\n5. At the very top Add the following using statements\nusing Microsoft.SharePoint.Utilities;\nusing System.Collections.ObjectModel;\n7. Under the class declaration add these three string constants which contain the names of the masterpages and the theme.\nSo my master page name is TomDaly.master, my search master page which I don’t have a custom one for yet is minimal.master, and my Custom Theme is called TomDaly\n8. Replace the public override void FeatureActivated, with\npublic override void FeatureActivated(SPFeatureReceiverProperties properties) { SPSite site = properties.Feature.Parent as SPSite; if (site != null) { using(SPWeb topLevelSite = site.RootWeb) { //Get the relative path string relativePath = topLevelSite.ServerRelativeUrl; if (!relativePath.EndsWith(\u0026#34;/\u0026#34;)) { relativePath += \u0026#34;/\u0026#34;; } //Get Theme collection from site and the them we want ReadOnlyCollection \u0026lt; ThmxTheme \u0026gt; themes = ThmxTheme.GetManagedThemes(site); ThmxTheme customTheme = null; foreach(ThmxTheme theme in themes) { if (theme.Name == themeName) { customTheme = theme; break; } } //Apply branding to each web in the site collection foreach(SPWeb web in site.AllWebs) { //Apply masterpage and logo if (web.WebTemplate == \u0026#34;SRCHCENTERLITE\u0026#34; || web.WebTemplate == \u0026#34;SRCHCEN\u0026#34; || web.WebTemplate == \u0026#34;SRCHCENTERFAST\u0026#34;) { web.CustomMasterUrl = relativePath + \u0026#34;_catalogs/masterpage/\u0026#34; + searchMasterPage; } else { web.MasterUrl = relativePath + \u0026#34;_catalogs/masterpage/\u0026#34; + masterPage; web.CustomMasterUrl = relativePath + \u0026#34;_catalogs/masterpage/\u0026#34; + masterPage; } web.AlternateCssUrl = \u0026#34;\u0026#34;; web.SiteLogoUrl = \u0026#34;\u0026#34;; web.UIVersion = 4; web.Update(); //Also apply the theme if (customTheme != null) { customTheme.ApplyTo(web, true); web.Update(); } } } } } 9. Replace the public override void FeatureDeactivated, with\npublic override void FeatureDeactivating(SPFeatureReceiverProperties properties) { SPSite site = properties.Feature.Parent as SPSite; if (site != null) { using(SPWeb topLevelSite = site.RootWeb) { //Get the relative path string relativePath = topLevelSite.ServerRelativeUrl; if (!relativePath.EndsWith(\u0026#34;/\u0026#34;)) { relativePath += \u0026#34;/\u0026#34;; } //Apply branding to each web the wen the site collection foreach(SPWeb web in site.AllWebs) { //Apply default masterpage and logo if (web.WebTemplate == \u0026#34;SRCHCENTERLITE\u0026#34; || web.WebTemplate == \u0026#34;SRCHCEN\u0026#34; || web.WebTemplate == \u0026#34;SRCHCENTERFAST\u0026#34;) { web.CustomMasterUrl = relativePath + \u0026#34;_catalogs/masterpage/minimal.master\u0026#34;; } else { web.MasterUrl = relativePath + \u0026#34;_catalogs/masterpage/v4.master\u0026#34;; web.CustomMasterUrl = relativePath + \u0026#34;_catalogs/masterpage/v4.master\u0026#34;; } web.AlternateCssUrl = \u0026#34;\u0026#34;; web.SiteLogoUrl = \u0026#34;\u0026#34;; web.Update(); //reset the theme back to default ThmxTheme.SetThemeUrlForWeb(web, null, true); web.Update(); } } } } 10. Click Save\n11. Hit F6, or Build -\u0026gt; Build Solution … to ensure that everything is correct and ok. You should receive the “Build Succeed” in the bottom left corner\nChanging the Site Url for Testing # Sometimes you want to create or re-use this package and you need to change the url of the site to deploy to.\n1. Click on the project and in the Properties window you’ll notice the Site URL\n2. Change that to your new site destination\n3. Click Save\nDeployment # If you’re testing on a development box, that visual studio is on then you can simply deploy through visual studio. Otherwise you’ll have to push out your solution with the .wsp file.\nDeploy from Visual Studio # 1. In the Build Menu, Select Deploy\nOR # Getting the .WSP for manual deployment # 1. In the Build menu, Select Build to ensure no errors\n2. In the Build menu, Select Package to generate the .WSP file\n3. The files will be in the project folder, and by default in the BIN\\DEBUG\nEither way once you deploy your solution should be available in the Site Collection Features Gallery as shown here\nSummary # So that’s how you setup a project for deploying branding assets. This is the typical setup I use but can change from client to client depending on their needs.\nDownload the entire project here.\n","date":"Oct 22, 2011","image":"","permalink":"/2011/10/22/deploying-master-pages-themes-via-a-feature-using-visual-studio-2010/","section":"Post","summary":"Following this guide you’ll see the process that goes behind creating the feature which commonly be used to deploy branding items such as Master Pages,…","title":"Deploying Master Pages \u0026 Themes via a Feature using Visual Studio 2010","type":"posts"},{"content":"Update 11/12/2013: Moved this to CodePlex – CleanMasterPages.CodePlex.com\nThis is a creation / compilation of the v4.master page which I use when branding a site that requires more of a SharePoint look where as sometimes the Starter Master Page takes away too much.\nThis master page contains everything V4 contains plus comments compiled from online blogs/articles, and ending comments to find the closing tags more easily. If you’re new to Master Pages then it may be worthwhile to take a look at this as its more cleanly indented and the descriptions could be of some value in learning.\nDownload this from my CodePlex page\n","date":"Oct 18, 2011","image":"","permalink":"/2011/10/18/clean-v4-master-page/","section":"Post","summary":"Update 11/12/2013: Moved this to CodePlex – CleanMasterPages.CodePlex.com This is a creation / compilation of the v4.master page which I use when branding a …","title":"Clean V4 Master Page","type":"posts"},{"content":"I’m very surprised and happy to announce that I am one of the many to receive the Microsoft Community Contributor Award for 2011! Not entirely sure how I got it but most likely due to my many postings/answers on TechNet/MSDN in the SharePoint forums. I consider this an honor to be recognized by Microsoft for my contributions to the community and look forward to continuing to contribute.\n","date":"May 3, 2011","image":"","permalink":"/2011/05/03/microsoft-community-contributor-award-2011/","section":"Post","summary":"I’m very surprised and happy to announce that I am one of the many to receive the Microsoft Community Contributor Award for 2011! Not entirely…","title":"Microsoft Community Contributor Award 2011","type":"posts"},{"content":"In this positing I’m going to cover utilizing the SPServices codeplex project to build a “Gallery” of sorts. In this demo we’ll be creating a picture gallery but ideally this code be used for any content. The main focus of this article is how to take advantage of the paging capability of the list web service. I found this to be a sparsely document capability and this demo might enlighten some people on how to use it.\nSome of you might say, “What’s the point of this. There are so many jQuery galleries and quite frankly they look better!”\nWell very true they do look better, and they might even have pagination. The problem with those is that you might have to load all the data on the page before it could perform its magic. This demo will only load the 6 items at a time, and this technique could be used on any type of object that needs pagination, and it’s more of a real world example of how to use the SPServices to make list web services calls using the CAML options for paging. With all that said, let’s get started. Just want the files? **Download the CEWP \u0026amp; gallery.js here\n**\n_Pre-Requisites:\n_\n_Download SPServices, and jQuery store them in a folder on your SharePoint site. I’m using SPServices 0.6.0 \u0026amp; jQuery 1.5.1\n_\nFirst I’ve created an image gallery called – “Gallery” Add more than 6 pictures to this library. Next edit the page and pop in a Content Editor Web Part, add in your script references. _** gallery.js is the name of our script file that we will be making **\n_\nNext add in the HTML structure that we’ll need. Lastly lets add just a touch of style Attached is the gallery.js, upload that where your other javascript files are and make sure the reference in step 3 is correct.\nLet’s take a look at the gallery.js to explain what its doing as well as show you what you can configure. I’ll be jumping around the script but I’m leaving the line numbers in there so you know where I am.\nThe rowLimit is the number of items to display per page, The list is the name of your List, and the query can be configure at the top of the script. (this can be expanded out to have a selected rowLimit or filters)\nThe following starts the creation of the gallery. Its contained in a function called Update(). Update will call the three functions to build the gallery. GetItems -\u0026gt; BuildNav -\u0026gt; UpdatePrevNext\nThis GetItems() function takes a position and a row limit and displays that set of items. The output of the items can be modified inside the $(xData.responseXML).find(“[nodeName=’z:row’]”).each(function(i) { });\nNext function BuildNav() builds out the navigation, it does this by making repetive calls to the web service with the row limit and the next position. In terms of performance, the more items you have and the smaller the row limit, then the more web service calls will be made. The less items and higher row limit, then the less web service calls will be made. This section also builds out the initial Prev \u0026amp; Next links.\nIn line 156 above the build nav will call GetNextPos and it will feed it the current position with a row limit in order for it to return the next position in the pagination\nLastly the script binds to the click events of the pagers, previous and next buttons.\nSo that’s it .. The finished product. A jQuery type solution to creating a paginated gallery type widget.\n**Download the CEWP \u0026amp; gallery.js here\n**\n","date":"Mar 31, 2011","image":"","permalink":"/2011/03/31/using-spservices-and-pagination-to-create-a-photo-gallery/","section":"Post","summary":"In this positing I’m going to cover utilizing the SPServices codeplex project to build a “Gallery” of sorts. In this demo we’ll be creating a…","title":"Using SPServices and Pagination to create a photo gallery","type":"posts"},{"content":"Next week ( 4/6/2011) I’ll be speaking at the NYC SharePoint User Group.\nI’ll be talking about “Getting Started with SharePoint Branding” specifically focusing on 2010.\nRead More and register here. http://www.sharepointusergroup.com/NewYork/default.aspx\n","date":"Mar 31, 2011","image":"","permalink":"/2011/03/31/speaking-sharepoint-nyc-user-group/","section":"Post","summary":"Next week ( 4/6/2011) I’ll be speaking at the NYC SharePoint User Group. I’ll be talking about “Getting Started with SharePoint Branding” specifically focusing …","title":"Speaking – SharePoint NYC User Group","type":"posts"},{"content":"I had an idea the other day to come up with a nice way to keep the ribbon away when I don’t really want to use it or see it. This is a basic concept of the idea which could use cleaning up or expanding upon. It’s a little snippet of jQuery that hides’ and shows the ribbon when the user click on an arrow in the top left corner.\nSomething like this.\nand when you click the arrow the ribbon would hide.\nok so to accomplish this you can either put this in your Masterpage or in a CEWP. The first line reference jQuery API and the second referencing our script.\nso the custom script can be downloaded here.\nThe top two lines of the script point to images files for the arrow. I’m using pretty crappy images in the picture above so they are not worth posting. Download or make two unique images and modify those top two lines in the script.\nThe rest of the script is basic just four functions FlipArrow, IsRibbonOpen, CloseRibbon and OpenRibbon. If you’d like to change the CSS of the arrow then take a look at line 7 of the ribbonUtil.js file.\nI have all the position, height and width, of the arrow images in there. So if you do plan to change them out them you’ll need to specify the height and width of your image file in there.\nSo this concept is pretty simple. It could be expanded out to save the state of the ribbon between pages, either by cookie or session variable. Also you could do something a little more graphically appealing. If you do try it out let me know how it goes.\n","date":"Jan 31, 2011","image":"","permalink":"/2011/01/31/concept-quick-hide-show-of-the-ribbon/","section":"Post","summary":"I had an idea the other day to come up with a nice way to keep the ribbon away when I don’t really want to…","title":"Concept – Quick Hide / Show of the Ribbon","type":"posts"},{"content":"This article specifically refers to the ScriptLink available in MOSS 07, as 2010 has other features such as script on demand which I will talk about another time.\nIf you are registering a script in your SharePoint Master Page, you basically have two options available to you. You can load it in this nice and fancy SharePoint way or just use the good old\nWe’ll i guess it really could depend on when you are looking to load your js file. With ScriptLink you have the ability to specify when you want the script to load asynchronously or after the after the page is ready. The ScriptLink will always look in /_LAYOUTS/1033 for the scripts.\nThis line would load the page and then load the script file. If you set Defer=”false” then the instant the page loaded and hit that line the .js file would execute.\nUsing Defer=”False” is basically the same as loading the script with this method. But with this method below you can specify a location other that LAYOUTS/1033 if you wanted to\nSo who cares when the file loads? well you might.. For instance if you’re using some JavaScript that relies on specific page elements to be there to operate on, then you would definitely want your script loaded after. This can be achieved easier just by specifying defer=true. Of course there are ways around this which you can code into your JavaScript or jQuery files to handle this as well.\nAnother difference with using the SharePoint:ScriptLink is that when the file gets loaded it must exist, otherwise you will get a SharePoint Error screen. This could help you ensure that SharePoint is seeing your js file.\nSo overall I don’t believe that there is much benefit from using the SharePoint:ScriptLink in Moss 2007, I personally use the to load all my custom js files. The jury is still out for 2010. I have yet to spend enough time experimenting with the newly added properties.\n","date":"Dec 16, 2010","image":"","permalink":"/2010/12/16/scriptlink-vs-whats-the-difference/","section":"Post","summary":"This article specifically refers to the ScriptLink available in MOSS 07, as 2010 has other features such as script on demand which I will talk about…","title":"ScriptLink vs Script tag, what’s the difference?","type":"posts"},{"content":"So this weekend I spent a whole lot of time trying to impliment the Jquery UI accoridion plug in on a site for one of our clients. I’ve done this in the past with different approaches to get it to work properly but this time I just had enough. There is an issue for most SharePoint ppl that alot of cool JQuery plugins don’t work or don’t work as displayed on their home websites. The problem usually has to deal with two specific things.. 1) the DOCTYPE or 2) some CSS issues.\nSo for those that may have tried to implement the JQuery accordion plugin you might have noticed a jumpy / flash/ glitch or whatever you wanna call it, only on the SlideUp portion. Basically this IS because of the DOCTYPE and there are certain options available to you which i’ll explain. So if you using the default OOTB masterpage.. there is no associated doctype this will make your IE renderin in Quirks mode and the official JQuery plugin won’t work. The exact problem is with the animation specifically when the height attribute goes to 0px. That’s when you will notice a full size flash or jump of the object being animated. If you set the height to 1px when you animate you won’t see the flash. The Simple Accordion plugin is animated to height 1px so it will work in Quirks mode.\nIf you are using the Heather Solomon masterpage (Depending on where you’ve downloading this from) it could have an incomplete DOCTYPE which still causes the browser to render in Quirks mode.\nThis is what i see in the HeathersBaseCollaborationMasterPage \u0026amp; HeathersBasePublishingMasterPage\nbut if you used HeathersBaseMasterPage you’ll get this. This is one of the correct DOCTYPEs supported by JQuery\nSo you could try to change the DOCTYPE to the HTML Strict or HTML Transitional but this might cause other issues with your current CSS. But then again maybe not. Or you could try to use this Simple Accordion type plugin i’ve created which will work in quirks mode because we are avoid the height issue as specified above.\nOK – NOW TO THE PLUGIN. There are 3 pieces the HTML, the JQuery and the CSS # #simpleAccordion h3 { cursor: pointer; } Section 1 # Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.\nSection 2 # Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In suscipit faucibus urna.\nSection 3 # Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis. Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui.\nList item one\nList item two\nList item three\nSection 4 # Cras dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia mauris vel est.\nSuspendisse eu nisl. Nullam ut libero. Integer dignissim consequat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.\nDownload the Script from here # Example to run it would be.. # $(document).ready(function() { $(\u0026#34;#simpleAccordion\u0026#34;).simpleAccordion({ duration: \u0026#39;fast\u0026#39;, oneOnly: false }); }); The options # duration – can be a time in ms or ‘slow’ ‘fast’ – see http://api.jquery.com/slideUp/ for more info. Default is ‘slow’.\noneOnly – true or false – True, only one panel can be open at a time, false for multiple panel open at the same time. Default ‘true’.\n","date":"Nov 28, 2010","image":"","permalink":"/2010/11/28/simple-accordion-type-plugin-for-jquery-works-w-quirks-mode/","section":"Post","summary":"So this weekend I spent a whole lot of time trying to impliment the Jquery UI accoridion plug in on a site for one of…","title":"Simple Accordion type plugin for JQuery – works w/ Quirks mode!","type":"posts"},{"content":"So you want to try to get your on your DROID? Well there is now some good news. Previously you might have been unable to authenticate to your SharePoint portal. This is because the browsers provided did not support the NTLM authentication schema.\nFireFox 4 Beta which is now available for the Android platform supports NTLM authentication. So now you can access your SharePoint site!\nGet the beta: http://www.mozilla.com/en-US/mobile/download/\n","date":"Nov 10, 2010","image":"","permalink":"/2010/11/10/sharepoint-access-on-your-droid-or-other-android-phone/","section":"Post","summary":"So you want to try to get your on your DROID? Well there is now some good news. Previously you might have been unable to…","title":"SharePoint Access on your DROID – or other android phone","type":"posts"},{"content":"A lot of people complain about issues with the People Picker when attempting to use an Ajax Update Panel with it because it just doesn’t work. In my particular problem I have a drop down list that does some processing to add people to the people picker list. When the drop-down is set to auto post back and there is no ajax, the page will post back and the people picker will have the new users in it.\nEverything works with the post back. However the new requirements by the customer was to add some ajax and elimnate this postback. So I added an update panel around the drop down list and people picker. This causes the people picker to stop working altogether. Online postings lead me to believe that the people picker has its own set or javascript which gets broken by Ajax.\nWhat can I do to make this work? I came up with a method of still using the update panel to update a normal text box, then have some client side processing to move the people in to the people picker. Let me elaborate. This is not the complete plug-play solution.. just the details.\nProcess:\nPage Loads Javascript registers -\u0026gt;EndRequestHandler Selected Index change on the drop down list Selected Index Change code Executes (this code not shown) Code generates user string in the format ‘DOMAIN\\user1;DOMAIN\\user2’. (These are definately good working accounts pre-checked by our code); AJAX partial page update occurs EndRequestHandler Code Executes stores the value of the txtAutoAddUsers text box pops the value into people picker ASPX – Code Behind .CS\n//txt box txtAutoAddUsers = new TextBox(); txtAutoAddUsers.ID = \u0026#34;txtAutoAddUsers\u0026#34;; //drop down ddlCategory = new DropDownList(); ddlCategory.ID = \u0026#34;ddlCategory\u0026#34;; ddlCategory.AutoPostBack = true; ddlCategory.SelectedIndexChanged += new EventHandler(ddlCategory_SelectedIndexChanged); //people picker peAssignRep = new PeopleEditor(); peAssignRep.ID = \u0026#34;peAssignRep\u0026#34;; peAssignRep.MultiSelect = true; //Update Panel UpdatePanel up = new UpdatePanel(); // pnRender .. is my asp panel where I\u0026#39;m going to put the update panel // Add the txt box and the ddl to the update panel base.Controls.Add(new LiteralControl(\u0026#34;\u0026#34;)); base.Controls.Add(new LiteralControl(\u0026#34;\u0026#34;)); pnlRender.Controls.Add(up); up.ContentTemplateContainer.Controls.Add(txtAutoAddUsers); up.ContentTemplateContainer.Controls.Add(ddlCategory); pnlRender.Controls.Add(peAssignRep); AutoAdd.JS File\nwindow.onload = go(); function go() { Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler); } function EndRequestHandler(sender, args) { var autoAddUsers = $(\u0026#34;input[id$=\u0026#39;txtAutoAddUsers\u0026#39;]\u0026#34;).val(); $(\u0026#34;div[id$=\u0026#39;_peAssignRep_upLevelDiv\u0026#39;]\u0026#34;).text(autoAddUsers); } function pageLoad() { $(\u0026#34;input[id$=\u0026#39;txtAutoAddUsers\u0026#39;]\u0026#34;).css(\u0026#34;display\u0026#34;, \u0026#34;none\u0026#34;); } Conclusion\nUsing the javascript to register the jquery code to run on the partial page update, enables us to take advantage on the update panel to generate users and populate a regular text box.\n","date":"Mar 28, 2010","image":"","permalink":"/2010/03/28/ajax-update-panel-the-people-picker/","section":"Post","summary":"A lot of people complain about issues with the People Picker when attempting to use an Ajax Update Panel with it because it just doesn’t…","title":"Ajax, Update Panel, \u0026 the People Picker","type":"posts"},{"content":"I’m posting this in case anyone is looking to easily get the time from your SharePoint server through the data view webpart. This will give you the hour, minute, second in the 24 hour format\nThe same approach could be used if you needed to gather the date in day, month, year.\n","date":"Feb 12, 2010","image":"","permalink":"/2010/02/12/40-2/","section":"Post","summary":"I’m posting this in case anyone is looking to easily get the time from your SharePoint server through the data view webpart. This will give…","title":"How can i get the current time in my data view web part","type":"posts"},{"content":"In the past month I’ve been heavily involved in dealing with SSPs. Sometimes rebuilding them, or migrating from a broken one to a new one. I’ve also spent alot of time with user profiles. My most recent project involved the colleague manager in which i’ve had to generate some tools to help setup my environment with dummy data for testing.\nIn the next couple blog postings I will be attempting to convey this information and code to you. In this specified posting I will talk about generating phony users for your environment and tools that will help do just that.\n**Note** These steps were performed on a standalone installation with a separate domain controller .\nFirst thing is to create Active Directory accounts so that SharePoint have a user to create a profile for. (Make sure you run this on the Domain Controller)\nFrom the command line, run this command:\nFOR /L %i in (1,1,500) DO NET USER MyUser%i P@ssw0rd /ADD\nThis will generate about 500 dummy users. Please read more about that here\nNext step is to perform an import in your Shared Service Provider to make sure that all the user get migrated into your SharePoint server.\nFrom the SharePoint server go to Central Administration Go to your Shared Service Provider Select User Profiles and Properties Select Start full import This will kick off the import of users to SharePoint. Once this is completed you’ll need this profile generator that I’ve created. The project can be downloaded here. By downloading the code you are responsible to test and validate it for your environment. I am not responsible for it. The executable is included.\nSo in the zip file you get the code along with the necessary information files. In the /bin/debug folder you’ll see everything you’ll need. Feel free to change any of the text files values.\nfn.txt \u0026gt; 3000 first names male \u0026amp; female ln.txt \u0026gt; 3000 last names jobs.txt \u0026gt; 3000 job titles location.txt \u0026gt; 3000 locations (cities) the ProfilePopulator.exe takes 7 inputs. Yes it’s a lot..but I will explain it below.\nprofilepopulator.exe [firstNameTextFile] [lastNameTextFile] [jobsTextFile] [locationTextFile] [http://siteurl] [userprefix] [numberOfAccounts]\nex: profilepopulator fn.txt ln.txt jobs.txt location.txt http://test.tomdaly.com TOMDALY\\MyUser 500 The first 4 should be pretty self explanatory. The 5 argument is the location of the site where the users exist (it will find the associated SSP), The 6 argument is the user prefix from that previous command. So for example my domain is TOMDALY and in the user creation command we create 500 users with the name MyUserX . Where X is a number. So for this argument you would provide TOMDALY\\MyUser . For the 7 argument provide the number of users that you’ve created in AD.\nWhen you run this you won’t get any pretty output.. It will just show you what user node it completed. When this is compeleted you’ll have 500 users with random profile information.\nFirst Name, Last Name, Preffered Name, Office Location, Job Title, Work Email \u0026amp; Work Phone.\n","date":"Jan 29, 2010","image":"","permalink":"/2010/01/29/generating-dummy-profiles-for-your-shared-service-provider/","section":"Post","summary":"In the past month I’ve been heavily involved in dealing with SSPs. Sometimes rebuilding them, or migrating from a broken one to a new one….","title":"Generating Dummy Profiles for your Shared Service Provider","type":"posts"},{"content":"This posting will demonstrate the power of jquery and the galleriffic jquery plugin to make your out of the box SharePoint picture gallery sexier. The other nice thing about this is that the thumbnail pictures are using the SharePoint generated thumbnails so the load time should be almost equivalent to the out of the box view.\nExample of the result:\nFirst thing is to get all the files in the right spot. All the necessary files can be downloaded here.\nEither open your site in SharePoint designer and put these files in the root site or create document repo’s and place them there..either way at the root site you need the following folders with the files provided.\n/css /scripts Next step is to navigate to your picture library and create a new view.\nThen click Site Actions -\u0026gt; Edit Page. Add a new web par -\u0026gt; Advanced -\u0026gt; Import. Then you want to import the .dwp file that was provided with the download.\nThat should be it, the look and feel is totally customizable with the css. Check the galleriffic website for details on how to customize the gallery even further.\n","date":"Dec 8, 2009","image":"","permalink":"/2009/12/08/jazzing-up-sharepoint-picture-gallery/","section":"Post","summary":"This posting will demonstrate the power of jquery and the galleriffic jquery plugin to make your out of the box SharePoint picture gallery sexier. The…","title":"Jazzing up SharePoint picture gallery","type":"posts"},{"content":"This post I’ll show you how to create a simple contact form using Jquery, web services, \u0026amp; the content editor. This form works best for internal web sites were bots will not repeatedly submit junk to your list. So let’s start.\nStep 1) Let’s start with your list.. we need to create a custom list, ContactMe. We will need a to add a couple new columns: (you can choice not required, b/c we’ll handle the validation in the jquery anyway)\nEmail – Single line of text Subject – Single line of text Message – Multiple Lines of Text You should now have something like this.\nStep 2) Ok now well setup the content editor webpart with the jquery references and start with the connecting to the list via webservices.\nIn Source Editor of the Content Editor Web Part\n** Pay special attention to line 32 which contains the list name, line 40 which contains your site / subsite url, line 54 which would redirect to a page on successful submission. **\n__Download the Code_ here_\nDownload the Code here\nEnd Result\nSo this takes care of submitting data through a content editor web part to a list, there are of course other better ways to do it. But if you are confined to using a content editor web part then this will help you.\n","date":"Nov 12, 2009","image":"","permalink":"/2009/11/12/contact-form-using-jquery-web-services-content-editor/","section":"Post","summary":"This post I’ll show you how to create a simple contact form using Jquery, web services, \u0026amp; the content editor. This form works best for…","title":"Contact form using Jquery, web services, \u0026 content editor","type":"posts"},{"content":" I'm Tom Daly, a Microsoft 365 consultant in Central New Jersey and Collaboration Director at SoHo Dragon. I've worked in the SharePoint and Microsoft 365 world since 2003, and this year marks my tenth year as a Microsoft MVP.\nMicrosoft MVP10x Most Valuable Professional Microsoft Global Community InitiativeMGCI Board Member Microsoft 365 \u0026amp; Power Platform CommunityPnP Initiative Coordinator What I focus on # Building solutions Designing and building real business solutions on Azure and Microsoft 365, from SharePoint intranets to custom web apps that replace tools teams have outgrown.\nAI enablement Helping organizations put AI to work in practical ways: reading and routing documents, automating busy work, and building AI into the tools people already use.\nM365 development SharePoint Framework (SPFx), Power Platform, Microsoft Graph and Azure Functions. This blog is where I share what I learn along the way.\nLeadership Leading the collaboration practice at SoHo Dragon, and leading CommunityDays.org, a nonprofit run with a team of volunteer organizers. I'm also a Board Member of the Microsoft Global Community Initiative (MGCI) and an initiative coordinator for the Microsoft 365 \u0026amp; Power Platform Community (PnP).\nCore values # 01 Community first Community events change careers, so they should be easy to find and free to list. That's why I started Community Days, a nonprofit that gives Microsoft community events a place to post and reach a bigger audience. The moment you put barriers in front of community, it stops being community.\n02 Share what you know Most of what I know, I learned from someone who took the time to write it down or teach it. Writing it up here is how I pay that forward.\n03 Give people a stage The best moment at any event is watching a first-time speaker realize they have something worth sharing. I want to create more of those moments.\n04 Build things that last Whether it's a client solution or a community platform, I'd rather build something simple that keeps working than something clever that doesn't.\nEvents # For the past 15+ years I\u0026rsquo;ve organized and helped run Microsoft community events in the New York, New Jersey and Pennsylvania area, from small local meetups to full community days.\nUpcoming # Nothing announced for this year yet. Find events near you on CommunityDays.org.\nPast highlights # AI Community Conference (AICO) New Jersey↗ 2026 Organizer AI Community Conference (AICO) DC↗ 2026 SpeakerPanel M365 Community Days NYC↗ 2023–2026 OrganizerIn person AI Community Conference (AICO) New York↗ 2025, 2026 Organizer M365 Philly↗ 2019–2021, 2024–2026 OrganizerSpeaker Experts Live US↗ 2025 Organizer M365 Community Days DC↗ 2025 Organizer Low Code No Code Power Platform Conference↗ 2024 Organizer CollabDays New England↗ 2022, 2024 Speaker M365 Community Days NJ↗ 2024 Organizer M365 Microsoft 365 Virtual Marathon 2022 SpeakerVirtual PP Power Platform Bootcamp 2022 Organizer CHI A Taste of M365 Chicago 2021 SpeakerVirtual CA Microsoft 365 Friday California 2021 SpeakerVirtual Azure Community Bootcamp↗ 2021 Organizer SPS SharePoint Saturday Omaha 2019 Speaker DC Cloud Saturday DC 2019 Speaker SD SharePoint \u0026amp; Office 365 Saturday San Diego 2019 Speaker Global M365 Developer Bootcamp NY, NJ \u0026amp; PA↗ 2019 OrganizerBootcamp O365 Office 365 Saturday NYC 2019 Organizer O365 Global Office 365 Developer Bootcamp NJ \u0026amp; NYC 2017, 2018 OrganizerBootcamp SPS SharePoint Saturday New England 2017, 2018 Speaker AZ Global Azure Bootcamp NJ 2017–2019 OrganizerBootcamp SPS SharePoint Saturday NYC 2009–2018 Organizer SPS SharePoint Saturday NJ 2005–2006 Organizer ","date":"","image":"","permalink":"/about-me/","section":"Page","summary":"","title":"About Me","type":"page"},{"content":" CommunityDays.org is a nonprofit, led by me and a team of organizers. It's a free place where community events across Microsoft technologies can post and reach a bigger audience: SharePoint Saturdays, Microsoft 365 (M365) Community Days, Power Platform and Azure events, and more. We're constantly improving the site and adding new features, and all of the work is done by volunteers.\nHow it started # When SPSEvents.org shut down, it left a real hole. That site had been the central hub for SharePoint Saturdays and Microsoft community events for years. Organizers relied on it, speakers browsed it to find events to submit to, and attendees used it to see what was happening near them.\nCommunity Days launched in October 2022 to fill that gap. SoHo Dragon sponsored the effort, Microsoft backed it, and Jeff Teper announced the launch at Ignite. The idea has always been simple: give organizers a place to list their events and give everyone else a place to find them.\nFree for organizers # The event listings, the call-for-sponsorship tools and the Speakerboard are all free. The moment you start putting barriers in front of community, it stops being community.\nVolunteer with us # We\u0026rsquo;re looking for volunteers. Community Days doesn\u0026rsquo;t run on one person. It runs on a group of people who show up: reviewing new events, promoting the platform, flagging bugs, and helping keep things funded and running.\nYou don\u0026rsquo;t need to be a developer, and you can give as much or as little time as you have. If you\u0026rsquo;d like to help, fill in the short volunteer form and tell us how you\u0026rsquo;d like to pitch in.\nVolunteer with Community Days\nThe people who make it work # Thomas Daly Heather Cook Jason Rivera Bryan Hart Arik Fletcher Jamel ABED Mirko Peters Christine Geluk Rob Windsor David Opdendries Manpreet Singh Sucheta Gawade Nikkia Carter Daniel Glenn Sedat Tum Oluwaseyi Oluwawumiju Jamie Willis-Rose Bob German David Leveille Sharon Weaver Shari L. Oswald, MVP, MCT Ed Williams David Patrick Ajay Bramhe - AZC Augustine Correa Vivian Voss Marcus Cadena Jonathan Weaver Nicholas Chang Haniel Croitoru Mike Maadarani Peter Ward Mark Rahn Martin Rövekamp Michal Ziemba Rachel Irabor Hal Hostetler Iqbal Nadiadi Ryan Schouten Emily Mancini Pat McGown Russ Onizuka Wes Preston Carl Karawani Diego Domingos da Silva Samir Makwana 46 people and counting. See everyone on the Community Days about page.\nOther ways to get involved # List your event on CommunityDays.org. Browse the Speakerboard at CommunityDays.org/speakerboard. Speakers who present at community events are listed automatically, so other organizers can find them. Sponsor a community event through the call-for-sponsorship listings. Just show up. Attend an event near you. That\u0026rsquo;s how most of us got started. Posts about Community Days # Community\rJust Showing Up: Why I Drive to Community Events I\u0026#39;m Not Running\rI\u0026rsquo;m not a Microsoft regional lead, but if a community event is a few hours\u0026rsquo; drive away, I\u0026rsquo;m probably there. What I do when …\nOct 19, 2026 · 5 min read\rCommunity\rM365 NYC 2026: Learning From the Person Next to You\rA short look back at M365 Community Days NYC 2026: full rooms, a scavenger hunt that gets strangers talking, and the volunteers who made it …\nOct 16, 2026 · 4 min read\rCommunity\rThree Years of Community Days: Built by the Community, For the Community\rCommunityDays.org turns three. A look back at how it started, what we\u0026rsquo;ve built, and the people who made it happen.\nOct 13, 2025 · 5 min read\rCommunity\rCommunity Days / Sessionize Key Deep Dive\rIntro In the previous article, we discussed creating a basic JSON-formatted API endpoint in Sessionize to integrate your event content, …\nNov 25, 2023 · 5 min read\rCommunity\rCreating a Sessionize Key for Community Days\rIn this walkthrough, we’ll guide you through the steps to create a Sessionize key, which is required by Community Days for integrating your …\nNov 25, 2023 · 2 min read\r","date":"","image":"","permalink":"/community-days/","section":"Page","summary":"","title":"Community Days","type":"page"},{"content":"Hoodies in a closet, a spreadsheet nobody trusts, and a stream of \"do we have any large ones left?\" emails. Company Store turns that into a real shop on a SharePoint page. Staff browse and order, and the people running it can see exactly what's left and what's going out.\nDownload the latest release View on GitHub Need help? Want changes? Have a question? From search to order placed, on a regular SharePoint page. Nothing extra to host Products, stock and orders all live in SharePoint lists on your own site. There's no server to run and no outside service to sign up for.\nOne button to set up Add the web part to a page and click Set up the store. It builds its own lists and permissions. Eight sample products let you try it before adding your own.\nFree items or priced Most company kit is free, so a price of 0 simply shows as Free. Set prices where you need them, in US dollars, euros or pounds. The store doesn't take payment.\nA shop people already know how to use # Nobody needs training for this. There\u0026rsquo;s a grid of products, a search box and a cart. Each card shows the price and how many are left, and the wording changes to a low-stock warning as a product runs out.\nSearch looks further than the product name. You can give each product extra search words, so someone typing \u0026ldquo;t-shirt\u0026rdquo; still finds the Reef crew tee.\nVariations that keep up with your stock # This is the part spreadsheets are worst at. A product can vary by up to three things, like color, size and fit, and every combination has its own stock count.\nSold-out choices are crossed out. They update as people choose. Here, black has run out of L and XXL, while white has those sizes but no XS. Each color can have its own photos. Click Black and the big picture switches to the black shirt. Some combinations can cost more. XXL here costs $24 instead of $22, so the card reads \u0026ldquo;From $22.00\u0026rdquo; and the price updates once a size is picked. Setting that up is mostly typing. Name the option, then type the choices with commas between them.\nThe store then builds a stock sheet with a box for every combination. Fill in the counts, or use Fill to put the same number in every box.\nA checkout that takes under a minute # Adding something opens the cart beside the product, so people can keep shopping or go straight to checkout.\nAt checkout they choose pick up at the office or delivery. The address boxes only show when they're needed, and the full name comes from their Microsoft 365 profile.\nThe store can remember an address for next time, and it can be forgotten with one click. Stock comes off when the order is placed. If an item sells out while it's in someone's cart, the cart updates and tells them.\n\u0026ldquo;Where\u0026rsquo;s my order?\u0026rdquo; answers itself # My orders shows everyone their own orders with a progress bar, from placed through to shipped or picked up. Tracking numbers from USPS, UPS, FedEx, DHL and OnTrac are recognized and become a link to that carrier\u0026rsquo;s tracking page.\nEverything the store team needs # Site owners and the people you make store managers get extra tabs along the top of the store, on the same page staff use. There\u0026rsquo;s no separate admin site to learn.\nProducts and stock # Every product is on one screen, with quick filters for what\u0026rsquo;s running low and what\u0026rsquo;s out of stock. Click Stock to change the numbers without opening the product, or switch a product off to hide it from the shop without deleting it.\nThe product editor holds the descriptions, details, search words and photos. Drop photos in, put them in order and choose a cover.\nOrders # Orders are filtered by where they\u0026rsquo;ve got to, and you can search them by number, name or item. Open one to see who it\u0026rsquo;s for, where it\u0026rsquo;s going and a history of everything that\u0026rsquo;s happened to it.\nNeed to change something? You can edit an order after it\u0026rsquo;s placed. Change a quantity and the stock is put back or taken to match.\nMoving an order along takes one change in the status menu. Mark it shipped, paste the tracking number, and the buyer gets an email saying it's on its way.\nThe store tells you which carrier it recognized, and you can untick the email if you'd rather not send one.\nYour words, not mine # Every message people see can be changed on a live preview. Click the outlined words and type. Fill-ins like {first name} and {order} are swapped for the real details on each order.\nThe same goes for the emails: one to the store team for each new order, and one each to the buyer when their order is confirmed, ready for pick-up or on its way. Order emails come from the buyer\u0026rsquo;s own mailbox, and status emails come from whoever moved the order along, so replies reach a real person.\nWho can see what # Staff can shop, and see only their own orders. Store managers run products, stock, orders and settings, without needing to own the site. Site owners can do all of that, and choose who the store managers are. Home addresses are locked down. Each person sees only their own saved address, and site owners and store managers can't open that list. What you\u0026rsquo;ll need to install it # Installing it takes a few minutes, but you\u0026rsquo;ll need the right people:\nA SharePoint Administrator to upload the package to your app catalog. A site owner to add the web part to a page and click Set up the store. A Global Administrator to approve the store\u0026rsquo;s request to send email, on the API access page in the SharePoint admin center. The request is for Microsoft Graph\u0026rsquo;s Mail.Send permission. Microsoft\u0026rsquo;s guide, Manage access to Microsoft Entra ID-secured APIs, shows where that page is and how to approve a request. That approval deserves a conversation with IT. It isn\u0026rsquo;t limited to the store: it lets any SharePoint add-on in your organization send email as the person using it. Without it, the store still works and orders still save, but no emails go out.\nThe README on GitHub has the full steps.\nTry it on your own site Company Store is free and open source under the MIT license. If you'd like help getting it running or changing it to suit your company, get in touch.\nDownload the latest release View on GitHub Need help? Want changes? Have a question? ","date":"","image":"","permalink":"/projects/company-store/","section":"Page","summary":"","title":"Company Store","type":"page"},{"content":"Whether you need a quick answer or a partner for a bigger build, I'd love to hear about it. I help teams build solutions on Microsoft 365 and Azure, put AI to work, and get unstuck on SharePoint and Power Platform.\nWhat kind of project? # Start here Quick intro 15 minutes\nNot sure what you need yet? Tell me what you're working on and we'll figure out the next step together.\nBook an intro ↓ Short-term Mentoring and guidance 30 minutes or 1 hour\nStuck on a problem, planning an approach, or want a second pair of eyes? Book a focused session on SharePoint, SPFx, Power Platform, Azure or AI.\nBook a session ↓ Long-term Project work Weeks to months\nCustom solutions, AI enablement, intranets, migrations and M365 development, from first sketch to launch. Start with an intro call and we'll scope it together.\nStart with an intro ↓ Book any session below, and its cost is credited toward any project work we take on together.\nBook a time # ","date":"","image":"","permalink":"/contact/","section":"Page","summary":"","title":"Contact","type":"page"},{"content":"Copilot shows people anything they already have access to. If a salary spreadsheet was shared with the whole company years ago, anyone can now get it just by asking. Copilot Readiness shows you where those files are before Copilot does, so you can turn it on with confidence.\nDownload the latest release View on GitHub Need help? Want changes? Have a question? Press one button and get a plain answer: what's exposed, where it is, and what to do about it. Shown with a made-up company. It never changes anything The scan only reads. Your sites, files and permissions stay exactly as they were.\nYour results stay with you It runs from your own PC and talks only to Microsoft. Nothing is sent anywhere else, including to me.\nNo expertise needed There's nothing to build and no commands to type. Double-click it, press Start scan, and the report opens by itself.\nKnow where you stand in one look # Every scan gives you a score out of 100, one for your SharePoint sites and one for your OneDrives. It\u0026rsquo;s something you can put in front of leadership, and something you can improve.\nBelow the scores, everything is sorted into four areas:\nOversharing: content open to everyone, \u0026ldquo;Anyone\u0026rdquo; links, company-wide links and public teams. This is the biggest risk with Copilot. External guests: people from outside your organization who can get into your sites. Sensitivity labels: sites and files with no label to say how carefully they should be handled. Stale or ownerless sites: sites nobody looks after, or nobody has used in a long time. Answer the question everyone asks # Search the report for what worries you most. Type \u0026ldquo;Salaries\u0026rdquo; and see every copy, where it lives, who can open it, and whether they can edit it.\nKnow exactly what to fix first # You don\u0026rsquo;t get a vague warning. Each kind of issue comes with a plain explanation of why it matters and what to do about it, worst first. Every row links straight to the site, folder or file, so you can go and fix it in a click.\nPrefer to work site by site? The same findings are listed by site, lowest score first, so your riskiest sites are at the top. Open one to see its owners, guests, label and every issue on it.\nHand the work to the right people # The Table view lets you sort and filter every finding, then download just the rows you need. You also get spreadsheets that open in Excel, including one for each OneDrive, so each person can be given only their own.\nFix the cause, not just the symptoms # Files get overshared because of how sharing is set up. So it also reviews your SharePoint sharing settings, showing what each one is now and what\u0026rsquo;s recommended. These don\u0026rsquo;t affect your score, but changing them means fewer problems in your next scan.\nSee your progress # Every scan is kept on your PC with its scores. Run it again after a clean-up and you can see the number move, and show others the difference.\nEasy to run, and easy on your tenant # The choices are already set for a good first scan, so you can just press the button. A quick scan takes a few seconds per site, and it tells you roughly how long it will take before you start.\nWhile it runs you can see what it\u0026rsquo;s doing, and how far along it is. At its normal pace it stays well under what Microsoft allows, and you can slow it down at any time. If a scan gets stopped, it can carry on where it left off.\nIs it safe to run? # Yes, and here\u0026rsquo;s exactly why:\nIt only reads. It doesn\u0026rsquo;t change anything in your Microsoft 365. You stay in control of access. A Global Administrator approves it once, on Microsoft\u0026rsquo;s own sign-in page. That page lists full control of SharePoint sites, because Microsoft requires that permission to read who has access to a site. The scan uses it only to read. OneDrive is your choice. It isn\u0026rsquo;t scanned unless you choose it. The report then lists the names of people\u0026rsquo;s shared files, so check that your organization is happy with that first. You can remove it completely when you\u0026rsquo;re done. You\u0026rsquo;ll need a Windows PC, and the free PowerShell 7 from Microsoft. The download includes a full guide with pictures for every step, and the README on GitHub has the details.\nFind out before Copilot does The scan is free to download and run. If you'd like someone to run it with you and walk your team through the results, book a readiness review.\nDownload the latest release View on GitHub Need help? Want changes? Have a question? ","date":"","image":"","permalink":"/projects/copilot-readiness/","section":"Page","summary":"","title":"Copilot Readiness","type":"page"},{"content":"Someone new starts on Monday. Did anyone order the laptop? Did they fill in their forms? Has their manager sat down with them yet? Onboarding Checklist puts all of that on a SharePoint page, so new starters know what to do next and everyone else can see how it's going.\nDownload the latest release View on GitHub Need help? Want changes? Have a question? A new starter works through their first month. Finishing the last task gets a little celebration. Nothing extra to host New starters, tasks and progress all live in SharePoint lists on your own site. There's no server to run and no outside service to sign up for.\nOne button to set up Add the web part to a page and click Set up onboarding. It builds its own lists and permissions, and offers a starter set of 17 common tasks to change to suit.\nNo extra permissions There's no permission request for IT to approve, because it only uses SharePoint. It doesn't send email either.\nA clear first month for the new starter # New starters open the page and see My checklist, a board with their tasks on it. They drag a task to another column or use the buttons on the card, and each move saves straight away.\nEvery card says which part of the month it belongs to, when it\u0026rsquo;s due and whether it\u0026rsquo;s late. Tasks can carry a link too, so \u0026ldquo;Set up two-step sign-in\u0026rdquo; takes them straight to the right page.\nFinish everything and the board gives way to a congratulations message and a burst of confetti. It\u0026rsquo;s a small touch, but it\u0026rsquo;s a nice way to end a first month.\nThe jobs that happen before they arrive # A good first day starts weeks earlier. Someone has to order the laptop, set up the account and assign the licenses.\nPick who does a task with Done by, and it becomes their job for every new starter. It shows on their own My tasks page, soonest due first, and the new starter never sees it. Write {{onboardee}} in the task\u0026rsquo;s name and the new starter\u0026rsquo;s name appears there instead.\nEveryone can see how it\u0026rsquo;s going # The New starters page lists everyone being onboarded, with their manager, start date and progress, and how many of their tasks are late. The progress includes the jobs other people are doing for them, so you can spot a held-up laptop order as easily as a late form.\nManagers get a My team page with just their own new starters.\nOpen someone to see their board. The Onboarding Team can move tasks for them, which helps when someone has done a task but hasn\u0026rsquo;t marked it. Managers can look but not move anything.\nAdding someone takes three boxes: who they are, who their manager is and their first day. Their checklist is ready straight away, with every date worked out from that first day. When they\u0026rsquo;ve finished, archive them. They come off the page, but their progress is kept.\nMake it fit your company # The starter set of tasks is only a beginning. Everything on the Tasks page can be changed, and changes show on every checklist straight away. Turn a task off to take it off every checklist without losing the record of who has done it.\nSections \u0026amp; columns sets how every board is laid out:\nSections are the parts of the checklist, like First day or First week. Each has a color, and its tasks are due a set number of days before or after the first day. A section can also have no due date at all. Columns are the steps on the board. Add, rename or reorder them, and choose which ones count as finished for the progress bar. Who can see what # New starters get their own board, without the jobs other people are doing for them. People doing jobs, like someone in IT, get My tasks. Managers get My team, and can follow along without changing anything. The Onboarding Team and site owners run the lot. Site owners choose who's on the team. Everyone else sees a short note saying there\u0026rsquo;s no checklist for them. Every list is hidden from the site contents and kept out of search, because people use them through the page.\nWhat you\u0026rsquo;ll need to install it # A SharePoint Administrator to upload the package to your app catalog. A site owner to add the web part to a page and click Set up onboarding. That\u0026rsquo;s it. There\u0026rsquo;s no permission request to approve.\nTwo things to know. New starters need a work account before they can be added. And it doesn\u0026rsquo;t send email, so you\u0026rsquo;ll need to tell people where to find the page.\nThe README on GitHub has the full steps.\nTry it on your own site Onboarding Checklist is free and open source under the MIT license. If you'd like help getting it running or changing it to suit your company, get in touch.\nDownload the latest release View on GitHub Need help? Want changes? Have a question? ","date":"","image":"","permalink":"/projects/onboarding-checklist/","section":"Page","summary":"","title":"Onboarding Checklist","type":"page"},{"content":"Ready-made tools for Microsoft 365 that I've built and use with clients. Each one runs inside your own tenant, with nothing extra to host.\nSharePoint web parts # Add one to any SharePoint page. A site owner clicks a single setup button and the web part makes the lists it needs.\nFree on GitHub Company Store An internal shop where staff browse company merch and place orders. Stock, orders and shipping addresses all live in SharePoint lists, and each order sends a confirmation email.\nSee more Free on GitHub Onboarding Checklist A first-month checklist for new starters, shown as a board they move tasks across. Managers see how far along their people are, and an onboarding team keeps the checklist up to date.\nSee more Coming soon Kaizen Board Continuous improvement for the whole team. Staff share ideas to make work better, vote on the ones they want, and follow each one from submitted to done. Reviewers own the stages.\nAsk about it Copilot Readiness # Scan script See what Copilot would expose, before you turn it on Copilot can find anything a person already has access to, including files that were overshared years ago. This scan runs from your own PC, and the results stay there. It checks every SharePoint site, and OneDrive if you choose, then scores what it finds.\nContent shared with everyone, and \"Anyone\" or company-wide links Guests who have access Sites and files missing sensitivity labels Sites with no owner, a single owner, or no recent use Choose a quick pass across every site, or a deep scan of the sites that worry you. The scan only reads; it never changes anything.\nSee more Book a readiness review Just for fun # Side project Trezor One glitching Hardware tinkering, a long way from SharePoint. Tools for timing a tiny dip in power to a Trezor One wallet's chip at just the right moment, so it skips its read protection and lets its memory be read.\nView on GitHub Side project PropCalc Pro A calculator for sizing up rental property deals. Plug in the price, rent and loan, and it shows the monthly cash flow and returns, tests the deal against higher rates or empty months, and charts the equity over thirty years. It runs in your browser with nothing to install.\nView on GitHub ","date":"","image":"","permalink":"/projects/","section":"Page","summary":"","title":"Projects","type":"page"}]